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 06a46c29..0eb43fbb 100644 --- a/core/src/main/java/org/springframework/ldap/config/ContextSourceParser.java +++ b/core/src/main/java/org/springframework/ldap/config/ContextSourceParser.java @@ -18,22 +18,25 @@ package org.springframework.ldap.config; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.parsing.BeanComponentDefinition; -import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser; import org.springframework.beans.factory.xml.BeanDefinitionParser; import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.ldap.CommunicationException; 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.transaction.compensating.manager.TransactionAwareContextSourceProxy; import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; +import org.springframework.util.xml.DomUtils; import org.w3c.dom.Element; -import org.w3c.dom.NodeList; -import static org.springframework.ldap.config.ParserUtils.NAMESPACE; +import java.util.HashSet; +import java.util.Set; + import static org.springframework.ldap.config.ParserUtils.getBoolean; import static org.springframework.ldap.config.ParserUtils.getInt; import static org.springframework.ldap.config.ParserUtils.getString; @@ -43,12 +46,14 @@ import static org.springframework.ldap.config.ParserUtils.getString; */ public class ContextSourceParser implements BeanDefinitionParser { private final static String ATT_ANONYMOUS_READ_ONLY = "anonymous-read-only"; + private final static String ATT_AUTHENTICATION_SOURCE_REF = "authentication-source-ref"; private final static String ATT_AUTHENTICATION_STRATEGY_REF = "authentication-strategy-ref"; private final static String ATT_BASE = "base"; private final static String ATT_PASSWORD = "password"; private final static String ATT_NATIVE_POOLING = "native-pooling"; private final static String ATT_REFERRAL = "referral"; private final static String ATT_URL = "url"; + private final static String ATT_BASE_ENV_PROPS_REF = "base-env-props-ref"; // pooling attributes private final static String ATT_MAX_ACTIVE = "max-active"; @@ -66,6 +71,7 @@ public class ContextSourceParser implements BeanDefinitionParser { private final static String ATT_VALIDATION_QUERY_BASE = "validation-query-base"; private final static String ATT_VALIDATION_QUERY_FILTER = "validation-query-filter"; private final static String ATT_VALIDATION_QUERY_SEARCH_CONTROLS_REF = "validation-query-search-controls-ref"; + private final static String ATT_NON_TRANSIENT_EXCEPTIONS = "non-transient-exceptions"; private final static String ATT_USERNAME = "username"; static final String DEFAULT_ID = "contextSource"; @@ -78,8 +84,6 @@ public class ContextSourceParser implements BeanDefinitionParser { String password = element.getAttribute(ATT_PASSWORD); String url = element.getAttribute(ATT_URL); - Assert.hasText(username, "username attribute must be specified"); - Assert.hasText(password, "password attribute must be specified"); Assert.hasText(url, "url attribute must be specified"); builder.addPropertyValue("userDn", username); @@ -89,35 +93,60 @@ public class ContextSourceParser implements BeanDefinitionParser { builder.addPropertyValue("base", getString(element, ATT_BASE, "")); builder.addPropertyValue("referral", getString(element, ATT_REFERRAL, null)); - builder.addPropertyValue("anonymousReadOnly", getBoolean(element, ATT_ANONYMOUS_READ_ONLY, false)); - builder.addPropertyValue("pooled", getBoolean(element, ATT_NATIVE_POOLING, false)); + boolean anonymousReadOnly = getBoolean(element, ATT_ANONYMOUS_READ_ONLY, false); + builder.addPropertyValue("anonymousReadOnly", anonymousReadOnly); + boolean nativePooling = getBoolean(element, ATT_NATIVE_POOLING, false); + builder.addPropertyValue("pooled", nativePooling); String authStrategyRef = element.getAttribute(ATT_AUTHENTICATION_STRATEGY_REF); if(StringUtils.hasText(authStrategyRef)) { builder.addPropertyReference("authenticationStrategy", authStrategyRef); } + String authSourceRef = element.getAttribute(ATT_AUTHENTICATION_SOURCE_REF); + if(StringUtils.hasText(authSourceRef)) { + builder.addPropertyReference("authenticationSource", authSourceRef); + } else { + Assert.hasText(username, "username attribute must be specified unless an authentication-source-ref explicitly configured"); + Assert.hasText(password, "password attribute must be specified unless an authentication-source-ref explicitly configured"); + } + + String baseEnvPropsRef = element.getAttribute(ATT_BASE_ENV_PROPS_REF); + if(StringUtils.hasText(baseEnvPropsRef)) { + builder.addPropertyReference("baseEnvironmentProperties", baseEnvPropsRef); + } + BeanDefinition targetContextSourceDefinition = builder.getBeanDefinition(); - targetContextSourceDefinition = applyPoolingIfApplicable(targetContextSourceDefinition, element); + targetContextSourceDefinition = applyPoolingIfApplicable(targetContextSourceDefinition, element, nativePooling); - - BeanDefinitionBuilder proxyBuilder = BeanDefinitionBuilder.rootBeanDefinition(TransactionAwareContextSourceProxy.class); - proxyBuilder.addConstructorArgValue(targetContextSourceDefinition); - AbstractBeanDefinition proxyBeanDefinition = proxyBuilder.getBeanDefinition(); + BeanDefinition actualContextSourceDefinition = targetContextSourceDefinition; + if (!anonymousReadOnly) { + BeanDefinitionBuilder proxyBuilder = BeanDefinitionBuilder.rootBeanDefinition(TransactionAwareContextSourceProxy.class); + proxyBuilder.addConstructorArgValue(targetContextSourceDefinition); + actualContextSourceDefinition = proxyBuilder.getBeanDefinition(); + } String id = getString(element, AbstractBeanDefinitionParser.ID_ATTRIBUTE, DEFAULT_ID); - parserContext.registerBeanComponent(new BeanComponentDefinition(proxyBeanDefinition, id)); + parserContext.registerBeanComponent(new BeanComponentDefinition(actualContextSourceDefinition, id)); - return proxyBeanDefinition; + return actualContextSourceDefinition; } - private BeanDefinition applyPoolingIfApplicable(BeanDefinition targetContextSourceDefinition, Element element) { - NodeList poolingChildren = element.getElementsByTagNameNS(NAMESPACE, Elements.POOLING); - if(poolingChildren.getLength() == 0) { + private BeanDefinition applyPoolingIfApplicable( + BeanDefinition targetContextSourceDefinition, + Element element, + boolean nativePooling) { + + Element poolingElement = DomUtils.getChildElementByTagName(element, Elements.POOLING); + if(poolingElement == null) { return targetContextSourceDefinition; } - Element poolingElement = (Element) poolingChildren.item(0); + if(nativePooling) { + throw new IllegalArgumentException( + String.format("%s cannot be enabled together with %s", ATT_NATIVE_POOLING, Elements.POOLING)); + } + BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(PoolingContextSource.class); builder.addPropertyValue("contextSource", targetContextSourceDefinition); @@ -160,6 +189,18 @@ public class ContextSourceParser implements BeanDefinitionParser { builder.addPropertyValue("timeBetweenEvictionRunsMillis", getInt(element, ATT_EVICTION_RUN_MILLIS, -1)); builder.addPropertyValue("numTestsPerEvictionRun", getInt(element, ATT_TESTS_PER_EVICTION_RUN, 3)); builder.addPropertyValue("minEvictableIdleTimeMillis", getInt(element, ATT_EVICTABLE_TIME_MILLIS, 1000 * 60 * 30)); - } + 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)); + } + } + + builder.addPropertyValue("nonTransientExceptions", nonTransientExceptionClasses); + } } diff --git a/core/src/main/java/org/springframework/ldap/config/TransactionManagerParser.java b/core/src/main/java/org/springframework/ldap/config/TransactionManagerParser.java index 0863070f..0a069c40 100644 --- a/core/src/main/java/org/springframework/ldap/config/TransactionManagerParser.java +++ b/core/src/main/java/org/springframework/ldap/config/TransactionManagerParser.java @@ -27,10 +27,9 @@ import org.springframework.ldap.transaction.compensating.support.DefaultTempEntr import org.springframework.ldap.transaction.compensating.support.DifferentSubtreeTempEntryRenamingStrategy; import org.springframework.util.Assert; import org.springframework.util.StringUtils; +import org.springframework.util.xml.DomUtils; import org.w3c.dom.Element; -import org.w3c.dom.NodeList; -import static org.springframework.ldap.config.ParserUtils.NAMESPACE; import static org.springframework.ldap.config.ParserUtils.getString; /** @@ -62,17 +61,15 @@ public class TransactionManagerParser implements BeanDefinitionParser { BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(ContextSourceTransactionManager.class); builder.addPropertyReference("contextSource", contextSourceRef); - NodeList defaultStrategyChildren = - element.getElementsByTagNameNS(NAMESPACE, Elements.DEFAULT_RENAMING_STRATEGY); - NodeList differentSubtreeChildren = - element.getElementsByTagNameNS(NAMESPACE, Elements.DIFFERENT_SUBTREE_RENAMING_STRATEGY); + Element defaultStrategyChild = DomUtils.getChildElementByTagName(element, Elements.DEFAULT_RENAMING_STRATEGY); + Element differentSubtreeChild = DomUtils.getChildElementByTagName(element, Elements.DIFFERENT_SUBTREE_RENAMING_STRATEGY); - if(defaultStrategyChildren.getLength() == 1) { - builder.addPropertyValue("renamingStrategy", parseDefaultRenamingStrategy((Element) defaultStrategyChildren.item(0))); + if(defaultStrategyChild != null) { + builder.addPropertyValue("renamingStrategy", parseDefaultRenamingStrategy(defaultStrategyChild)); } - if(differentSubtreeChildren.getLength() == 1) { - builder.addPropertyValue("renamingStrategy", parseDifferentSubtreeRenamingStrategy((Element) differentSubtreeChildren.item(0))); + if(differentSubtreeChild != null) { + builder.addPropertyValue("renamingStrategy", parseDifferentSubtreeRenamingStrategy(differentSubtreeChild)); } String id = getString(element, AbstractBeanDefinitionParser.ID_ATTRIBUTE, DEFAULT_ID); diff --git a/core/src/main/java/org/springframework/ldap/core/support/AbstractContextSource.java b/core/src/main/java/org/springframework/ldap/core/support/AbstractContextSource.java index 1920e88d..9e1d9b22 100644 --- a/core/src/main/java/org/springframework/ldap/core/support/AbstractContextSource.java +++ b/core/src/main/java/org/springframework/ldap/core/support/AbstractContextSource.java @@ -665,6 +665,5 @@ public abstract class AbstractContextSource implements BaseLdapPathContextSource public String getCredentials() { return password; } - } } diff --git a/core/src/main/java/org/springframework/ldap/transaction/compensating/manager/ContextSourceTransactionManagerDelegate.java b/core/src/main/java/org/springframework/ldap/transaction/compensating/manager/ContextSourceTransactionManagerDelegate.java index 6f19f1a7..bfad7bb0 100644 --- a/core/src/main/java/org/springframework/ldap/transaction/compensating/manager/ContextSourceTransactionManagerDelegate.java +++ b/core/src/main/java/org/springframework/ldap/transaction/compensating/manager/ContextSourceTransactionManagerDelegate.java @@ -19,6 +19,7 @@ package org.springframework.ldap.transaction.compensating.manager; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.ldap.core.ContextSource; +import org.springframework.ldap.core.support.AbstractContextSource; import org.springframework.ldap.transaction.compensating.LdapCompensatingTransactionOperationFactory; import org.springframework.ldap.transaction.compensating.TempEntryRenamingStrategy; import org.springframework.ldap.transaction.compensating.support.DefaultTempEntryRenamingStrategy; @@ -68,6 +69,14 @@ public class ContextSourceTransactionManagerDelegate extends } else { this.contextSource = contextSource; } + + if (contextSource instanceof AbstractContextSource) { + AbstractContextSource abstractContextSource = (AbstractContextSource) contextSource; + if(abstractContextSource.isAnonymousReadOnly()) { + throw new IllegalArgumentException( + "Compensating LDAP transactions cannot be used when context-source is anonymous-read-only"); + } + } } public ContextSource getContextSource() { 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 e4371965..1f4cf5be 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 @@ -39,13 +39,13 @@ - The base DN. If specified, all LDAP operations on contexts retrieved from this ContextSource will + The base DN. If configured, all LDAP operations on contexts retrieved from this ContextSource will be relative to this DN. Default is an empty distinguished name (i.e. all operations will be relative to the directory root). - + The password to use for authentication. @@ -82,7 +82,7 @@ - + The username (principal) to use for authentication. This will normally be the distinguished name @@ -90,6 +90,14 @@ + + + + Reference to a Map of custom environment properties that should supplied with the environment + sent to the DirContext on construction. + + + @@ -240,6 +248,14 @@ + + + + Id of the SearchControls instance to use for searches. Default is searchScope=OBJECT_SCOPE; + countLimit: 1; timeLimit: 500; returningAttributes: [objectclass]. + + + diff --git a/core/src/test/java/org/springframework/ldap/config/DummyAuthenticationSource.java b/core/src/test/java/org/springframework/ldap/config/DummyAuthenticationSource.java new file mode 100644 index 00000000..71a1439b --- /dev/null +++ b/core/src/test/java/org/springframework/ldap/config/DummyAuthenticationSource.java @@ -0,0 +1,34 @@ +/* + * 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.config; + +import org.springframework.ldap.core.AuthenticationSource; + +/** + * @author Mattias Hellborg Arthursson + */ +public class DummyAuthenticationSource implements AuthenticationSource { + @Override + public String getPrincipal() { + throw new UnsupportedOperationException(); + } + + @Override + public String getCredentials() { + throw new UnsupportedOperationException(); + } +} diff --git a/core/src/test/java/org/springframework/ldap/config/DummyAuthenticationStrategy.java b/core/src/test/java/org/springframework/ldap/config/DummyAuthenticationStrategy.java new file mode 100644 index 00000000..fff6b1e3 --- /dev/null +++ b/core/src/test/java/org/springframework/ldap/config/DummyAuthenticationStrategy.java @@ -0,0 +1,38 @@ +/* + * 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.config; + +import org.springframework.ldap.core.support.DirContextAuthenticationStrategy; + +import javax.naming.NamingException; +import javax.naming.directory.DirContext; +import java.util.Hashtable; + +/** + * @author Mattias Hellborg Arthursson + */ +public class DummyAuthenticationStrategy implements DirContextAuthenticationStrategy { + @Override + public void setupEnvironment(Hashtable env, String userDn, String password) throws NamingException { + throw new UnsupportedOperationException(); + } + + @Override + public DirContext processContextAfterCreation(DirContext ctx, String userDn, String password) throws NamingException { + throw new UnsupportedOperationException(); + } +} 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 1018c8e3..c6a32086 100644 --- a/core/src/test/java/org/springframework/ldap/config/LdapTemplateNamespaceHandlerTest.java +++ b/core/src/test/java/org/springframework/ldap/config/LdapTemplateNamespaceHandlerTest.java @@ -18,10 +18,13 @@ package org.springframework.ldap.config; import org.apache.commons.pool.impl.GenericKeyedObjectPool; import org.junit.Test; +import org.springframework.beans.BeansException; import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.ldap.core.AuthenticationSource; import org.springframework.ldap.core.ContextSource; import org.springframework.ldap.core.LdapTemplate; 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.support.LdapUtils; @@ -32,7 +35,10 @@ import org.springframework.ldap.transaction.compensating.support.DefaultTempEntr import org.springframework.ldap.transaction.compensating.support.DifferentSubtreeTempEntryRenamingStrategy; import org.springframework.transaction.PlatformTransactionManager; +import javax.naming.CannotProceedException; +import javax.naming.CommunicationException; import javax.naming.directory.SearchControls; +import java.util.Set; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; @@ -75,6 +81,48 @@ public class LdapTemplateNamespaceHandlerTest { assertEquals(SearchControls.SUBTREE_SCOPE, getInternalState(ldapTemplate, "defaultSearchScope")); } + @Test + public void verifyThatAnonymousReadOnlyContextWillNotBeWrappedInProxy() { + ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("/ldap-namespace-config-anonymous-read-only.xml"); + ContextSource contextSource = ctx.getBean(ContextSource.class); + + assertNotNull(contextSource); + assertTrue(contextSource instanceof LdapContextSource); + assertEquals(Boolean.TRUE, getInternalState(contextSource, "anonymousReadOnly")); + } + + @Test(expected = BeansException.class) + public void verifyThatAnonymousReadOnlyAndTransactionalThrowsException() { + new ClassPathXmlApplicationContext("/ldap-namespace-config-anonymous-read-only-and-transactions.xml"); + } + + @Test(expected = BeansException.class) + public void verifyThatMissingUsernameThrowsException() { + new ClassPathXmlApplicationContext("/ldap-namespace-config-missing-username.xml"); + } + + @Test(expected = BeansException.class) + public void verifyThatMissingPasswordThrowsException() { + new ClassPathXmlApplicationContext("/ldap-namespace-config-missing-password.xml"); + } + + @Test + public void verifyReferences() { + ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("/ldap-namespace-config-references.xml"); + ContextSource outerContextSource = ctx.getBean(ContextSource.class); + AuthenticationSource authenticationSource = ctx.getBean(AuthenticationSource.class); + DirContextAuthenticationStrategy authenticationStrategy = ctx.getBean(DirContextAuthenticationStrategy.class); + Object baseEnv = ctx.getBean("baseEnvProps"); + + assertNotNull(outerContextSource); + + assertTrue(outerContextSource instanceof TransactionAwareContextSourceProxy); + ContextSource contextSource = ((TransactionAwareContextSourceProxy) outerContextSource).getTarget(); + + assertSame(authenticationSource, getInternalState(contextSource, "authenticationSource")); + assertSame(authenticationStrategy, getInternalState(contextSource, "authenticationStrategy")); + assertEquals(baseEnv, getInternalState(contextSource, "baseEnv")); + } @Test public void verifyParseWithCustomValues() { ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("/ldap-namespace-config-values.xml"); @@ -93,7 +141,7 @@ public class LdapTemplateNamespaceHandlerTest { assertEquals("apassword", getInternalState(contextSource, "password")); assertArrayEquals(new String[]{"ldap://localhost:389"}, (Object[]) getInternalState(contextSource, "urls")); assertEquals(Boolean.TRUE, getInternalState(contextSource, "pooled")); - assertEquals(Boolean.TRUE, getInternalState(contextSource, "anonymousReadOnly")); + assertEquals(Boolean.FALSE, getInternalState(contextSource, "anonymousReadOnly")); assertEquals("follow", getInternalState(contextSource, "referral")); assertSame(authenticationStrategy, getInternalState(contextSource, "authenticationStrategy")); @@ -164,6 +212,7 @@ public class LdapTemplateNamespaceHandlerTest { } @Test + @SuppressWarnings("unchecked") public void verifyParsePoolingDefaults() { ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("/ldap-namespace-config-pooling-defaults.xml"); @@ -178,6 +227,10 @@ public class LdapTemplateNamespaceHandlerTest { Object objectFactory = getInternalState(pooledContextSource, "dirContextPoolableObjectFactory"); assertNotNull(getInternalState(objectFactory, "contextSource")); assertNull(getInternalState(objectFactory, "dirContextValidator")); + Set> nonTransientExceptions = + (Set>) getInternalState(objectFactory, "nonTransientExceptions"); + assertEquals(1, nonTransientExceptions.size()); + assertTrue(nonTransientExceptions.contains(CommunicationException.class)); GenericKeyedObjectPool objectPool = (GenericKeyedObjectPool) getInternalState(pooledContextSource, "keyedObjectPool"); assertEquals(8, objectPool.getMaxActive()); @@ -208,6 +261,7 @@ public class LdapTemplateNamespaceHandlerTest { } @Test + @SuppressWarnings("unchecked") public void verifyParsePoolingValidationSet() { ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("/ldap-namespace-config-pooling-test-specified.xml"); @@ -230,5 +284,16 @@ public class LdapTemplateNamespaceHandlerTest { 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 verifyParseWithPoolingAndNativePoolingWillFail() { + new ClassPathXmlApplicationContext("/ldap-namespace-config-pooling-with-native.xml"); } } diff --git a/core/src/test/resources/ldap-namespace-config-anonymous-read-only-and-transactions.xml b/core/src/test/resources/ldap-namespace-config-anonymous-read-only-and-transactions.xml new file mode 100644 index 00000000..869189ab --- /dev/null +++ b/core/src/test/resources/ldap-namespace-config-anonymous-read-only-and-transactions.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/core/src/test/resources/ldap-namespace-config-anonymous-read-only.xml b/core/src/test/resources/ldap-namespace-config-anonymous-read-only.xml new file mode 100644 index 00000000..1ae1fbbc --- /dev/null +++ b/core/src/test/resources/ldap-namespace-config-anonymous-read-only.xml @@ -0,0 +1,15 @@ + + + + + + + \ No newline at end of file diff --git a/core/src/test/resources/ldap-namespace-config-defaults.xml b/core/src/test/resources/ldap-namespace-config-defaults.xml index 54ddf74d..f8c2aa7b 100644 --- a/core/src/test/resources/ldap-namespace-config-defaults.xml +++ b/core/src/test/resources/ldap-namespace-config-defaults.xml @@ -2,8 +2,8 @@ - + xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd + http://www.springframework.org/schema/ldap http://www.springframework.org/schema/ldap/spring-ldap.xsd"> diff --git a/core/src/test/resources/ldap-namespace-config-missing-password.xml b/core/src/test/resources/ldap-namespace-config-missing-password.xml new file mode 100644 index 00000000..3b7d3567 --- /dev/null +++ b/core/src/test/resources/ldap-namespace-config-missing-password.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/core/src/test/resources/ldap-namespace-config-missing-username.xml b/core/src/test/resources/ldap-namespace-config-missing-username.xml new file mode 100644 index 00000000..83683d0c --- /dev/null +++ b/core/src/test/resources/ldap-namespace-config-missing-username.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/core/src/test/resources/ldap-namespace-config-pooling-test-specified.xml b/core/src/test/resources/ldap-namespace-config-pooling-test-specified.xml index 704c4a0c..4b60d2ee 100644 --- a/core/src/test/resources/ldap-namespace-config-pooling-test-specified.xml +++ b/core/src/test/resources/ldap-namespace-config-pooling-test-specified.xml @@ -15,7 +15,8 @@ tests-per-eviction-run="22" validation-query-base="ou=test" validation-query-filter="objectclass=person" - validation-query-search-controls-ref="searchControls"/> + validation-query-search-controls-ref="searchControls" + non-transient-exceptions="javax.naming.CannotProceedException,javax.naming.CommunicationException" /> diff --git a/core/src/test/resources/ldap-namespace-config-pooling-with-native.xml b/core/src/test/resources/ldap-namespace-config-pooling-with-native.xml new file mode 100644 index 00000000..7ab706ae --- /dev/null +++ b/core/src/test/resources/ldap-namespace-config-pooling-with-native.xml @@ -0,0 +1,17 @@ + + + + + + + + + + \ No newline at end of file diff --git a/core/src/test/resources/ldap-namespace-config-references.xml b/core/src/test/resources/ldap-namespace-config-references.xml new file mode 100644 index 00000000..fde4eba5 --- /dev/null +++ b/core/src/test/resources/ldap-namespace-config-references.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/core/src/test/resources/ldap-namespace-config-values.xml b/core/src/test/resources/ldap-namespace-config-values.xml index 93463353..8106946b 100644 --- a/core/src/test/resources/ldap-namespace-config-values.xml +++ b/core/src/test/resources/ldap-namespace-config-values.xml @@ -11,7 +11,6 @@ url="ldap://localhost:389" username="uid=admin" base="dc=261consulting,dc=com" - anonymous-read-only="true" authentication-strategy-ref="authenticationStrategy" native-pooling="true" referral="follow" /> diff --git a/src/docbkx/basic.xml b/src/docbkx/basic.xml index 40d513dc..c1d7b0f2 100644 --- a/src/docbkx/basic.xml +++ b/src/docbkx/basic.xml @@ -402,21 +402,6 @@ public class PersonDaoImpl implements PersonDao { It is recommended that you review the Spring LDAP sample applications included in the release distribution for best-practice - illustrations of the features of this library. A description of each - sample is provided below: - - - - spring-ldap-person - the sample demonstrating most - features. - - - - spring-ldap-article - the sample application that was written - to accompany a java.net - article about Spring LDAP. - - + illustrations of the features of this library. diff --git a/src/docbkx/configuration.xml b/src/docbkx/configuration.xml index a5dd3b35..bf84741b 100644 --- a/src/docbkx/configuration.xml +++ b/src/docbkx/configuration.xml @@ -1,205 +1,362 @@ Configuration + + Introduction + The recommended way of configuring Spring LDAP is using the custom XML configuration namespace. + In order to make this available you need to include the Spring LDAP namespace declaration in your + bean file, e.g.: + + +<beans xmlns="http://www.springframework.org/schema/beans" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:ldap="http://www.springframework.org/schema/ldap" + xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd + http://www.springframework.org/schema/ldap http://www.springframework.org/schema/ldap/spring-ldap.xsd"> + + + - - ContextSource Configuration + + ContextSource Configuration - There are several properties in AbstractContextSource - (superclass of DirContextSource and LdapContextSource) - that can be used to modify its behaviour. - - - LDAP Server URLs - - The URL of the LDAP server is specified using the url property. - The URL should be in the format ldap://myserver.example.com:389. - For SSL access, use the ldaps protocol and the appropriate port, e.g. - ldaps://myserver.example.com:636 - It is possible to configure multiple alternate LDAP servers using the - urls property. In this case, supply all server urls in a String - array to the urls property. - - - - Base LDAP path - - It is possible to specify the root context for all LDAP operations using the - base property of AbstractContextSource. - When a value has been specified to this property, all Distinguished Names supplied to and received from LDAP operations - will be relative to the LDAP path supplied. This can significantly simplify working against the LDAP - tree; however there are several occations when you will need to have access to the base path. - For more information on this, please refer to - + + The ContextSource is defined using a <ldap:context-source> + tag. The simplest possible context-source declaration requires you to specify a + server url, a username, and a password: + + Simplest possible context-source declaration + ]]> + + This will create an LdapContextSource with default values (see below), + and the url and authentication information as specified. + + + The configurable attributes on context-source are as follows (required attributes marked with *): + + + ContextSource Configuration Attributes + + + + + + + Attribute + Default + Description + + + + + + id + + + contextSource + + + The id of the created bean. + + + + + username + + + + + The username (principal) to use when authenticating with the LDAP server. + This will usually be the distinguished name of an admin user (e.g. + cn=Administrator, but may differ depending on server + and authentication method. + Required if authentication-source-ref is not explicitly configured. + + + + + password + + + + + The password (credentials) to use when authenticating with the LDAP server. + Required if authentication-source-ref is not explicitly configured. + + + + + url * + + + + + The URL of the LDAP server to use. The URL should be in the format + ldap://myserver.example.com:389. + For SSL access, use the ldaps protocol and the appropriate port, e.g. + ldaps://myserver.example.com:636. If fail-over functionality is desired, + more than one URL can be specified, separated using comma (,). + + + + + base + + + LdapUtils.emptyLdapName() + + + The base DN. When this attribute has been configured, all Distinguished Names supplied to + and received from LDAP operations will be relative to the sepecified LDAP path. + This can significantly simplify working against the LDAP tree; however there are several + occasions when you will need to have access to the base path. + For more information on this, please refer to + + + + + anonymous-read-only + + + false + + + Defines whether read-only operations will be performed using an anonymous (unauthenticated) context. + Note that setting this parameter to true + together with the compensating transaction support is not supported and will be rejected. + + + + + referral + + + null + + + Defines the strategy to handle referrals, as described + here. + Valid values are: + + ignore + follow + throw + + + + + + native-pooling + + + false + + + Specify whether native Java LDAP connection pooling should be used. Consider using + Spring LDAP connection pooling instead. See for more information. + + + + + authentication-source-ref + + + A SimpleAuthenticationSource instance. + + + Id of the AuthenticationSource instance to use (see below). + + + + + authentication-strategy-ref + + + A SimpleDirContextAuthenticationStrategy instance. + + + Id of the DirContextAuthenticationStrategy instance to use (see below). + + + + + base-env-props-ref + + + A SimpleDirContextAuthenticationStrategy instance. + + + Reference to a Map of custom environment properties that should supplied with the environment + sent to the DirContext on construction. + + + + +
DirContext Authentication - When DirContext instances are created to be used for performing - operations on an LDAP server these contexts often need to be authenticated. There are - different options for configuring this using Spring LDAP, described in this chapter. + + When DirContext instances are created to be used for performing + operations on an LDAP server these contexts often need to be authenticated. There are + different options for configuring this using Spring LDAP, described in this chapter. - This section refers to authenticating contexts in the core functionality - of the ContextSource - to construct DirContext instances - for use by LdapTemplate. LDAP is commonly used for the sole purpose - of user authentication, and the ContextSource may be used for that as - well. This process is discussed in . - + + + This section refers to authenticating contexts in the core functionality + of the ContextSource - to construct DirContext instances + for use by LdapTemplate. LDAP is commonly used for the sole purpose + of user authentication, and the ContextSource may be used for that as + well. This process is discussed in . + + + - Authenticated contexts are created for both read-only and - read-write operations by default. You specify - userDn and password of the LDAP - user to be used for authentication on the - ContextSource. + + Authenticated contexts are created for both read-only and + read-write operations by default. You specify + username and password of the LDAP + user to be used for authentication on the + context-source element. - - The userDn needs to be the full - Distinguished Name (DN) of the user from the root of the LDAP tree, - regardless of whether a base LDAP path has been supplied to - the ContextSource. - - - Some LDAP server setups allow anonymous read-only access. If you - want to use anonymous Contexts for read-only operations, set the - anonymousReadOnly property to - true. + + + If username is the dn of an LDAP user, it needs to be the full + Distinguished Name (DN) of the user from the root of the LDAP tree, + regardless of whether a base LDAP path has been specified on + the context-source element. + + + + + Some LDAP server setups allow anonymous read-only access. If you + want to use anonymous Contexts for read-only operations, set the + anonymous-read-only attribute to + true. + Custom DirContext Authentication Processing - The default authentication mechanism used in Spring LDAP is SIMPLE authentication. - This means that in the user DN (as specified to the userDn property) and - the credentials (as specified to the password) are set in - the Hashtable sent to the DirContext implementation constructor. - There are many occasions when this processing is not sufficient. For instance, - LDAP Servers are commonly set up to only accept communication on a secure TLS channel; - there might be a need to use the particular LDAP Proxy Auth mechanism, etc. - It is possible to specify an alternative authentication mechanism by supplying a - DirContextAuthenticationStrategy implementation to the ContextSource - in the configuration. + + The default authentication mechanism used in Spring LDAP is SIMPLE authentication. + This means that the principal (as specified to the username attribute) and + the credentials (as specified to the password) are set in + the Hashtable sent to the DirContext implementation constructor. + + + There are many occasions when this processing is not sufficient. For instance, + LDAP Servers are commonly set up to only accept communication on a secure TLS channel; + there might be a need to use the particular LDAP Proxy Auth mechanism, etc. + + + It is possible to specify an alternative authentication mechanism by supplying a + DirContextAuthenticationStrategy implementation reference + to the context-source element using the authentication-strategy-ref + attribute. + TLS - Spring LDAP provides two different configuration options for LDAP servers requiring TLS secure - channel communication: DefaultTlsDirContextAuthenticationStrategy and - ExternalTlsDirContextAuthenticationStrategy. Both these - implementations will negotiate a TLS channel on the target connection, but they differ in the actual authentication mechanism. - Whereas the DefaultTlsDirContextAuthenticationStrategy will apply SIMPLE authentication - on the secure channel (using the specified userDn and password), - the ExternalDirContextAuthenticationStrategy will use EXTERNAL SASL authentication, - applying a client certificate configured using system properties for authentication. + + Spring LDAP provides two different configuration options for LDAP servers requiring TLS secure + channel communication: DefaultTlsDirContextAuthenticationStrategy and + ExternalTlsDirContextAuthenticationStrategy. Both these + implementations will negotiate a TLS channel on the target connection, but they differ in the actual authentication mechanism. + Whereas the DefaultTlsDirContextAuthenticationStrategy will apply SIMPLE authentication + on the secure channel (using the specified userDn and password), + the ExternalDirContextAuthenticationStrategy will use EXTERNAL SASL authentication, + applying a client certificate configured using system properties for authentication. + - Since different LDAP server implementations respond differently to explicit shutdown of the - TLS channel (some servers require the connection be shutdown gracefully; others do not support it), - the TLS DirContextAuthenticationStrategy implementations support specifying - the shutdown behavior using the shutdownTlsGracefully parameter. If this - property is set to false (the default), no explicit TLS shutdown will happen; - if it is true, Spring LDAP will try to shutdown the TLS channel gracefully - before closing the target context. + + Since different LDAP server implementations respond differently to explicit shutdown of the + TLS channel (some servers require the connection be shutdown gracefully; others do not support it), + the TLS DirContextAuthenticationStrategy implementations support specifying + the shutdown behavior using the shutdownTlsGracefully parameter. If this + property is set to false (the default), no explicit TLS shutdown will happen; + if it is true, Spring LDAP will try to shutdown the TLS channel gracefully + before closing the target context. + - When working with TLS connections you need to make sure that the native LDAP - Pooling functionality is turned off. As of release 1.3, the default setting is off. For earlier - versions, simply set the pooled property to false. This is - particularly important if shutdownTlsGracefully is set to false. - However, since the TLS channel negotiation process is quite expensive, great performance benefits will - be gained by using the Spring LDAP Pooling Support, described in . - + + + When working with TLS connections you need to make sure that the native LDAP + Pooling functionality (as specified using the native-pooling attribute + is turned off. This is particularly important if shutdownTlsGracefully + is set to false. However, since the TLS channel negotiation process is + quite expensive, great performance benefits will be gained by using the Spring LDAP + Pooling Support, described in . + + Custom Principal and Credentials Management - While the user name (i.e. user DN) and password used for - creating an authenticated Context are static by - default - the ones set on the ContextSource on - startup will be used throughout the lifetime of the - ContextSource - there are however several cases in - which this is not the desired behaviour. A common scenario is that the - principal and credentials of the current user should be used when - executing LDAP operations for that user. The default behaviour can be - modified by supplying a custom AuthenticationSource - implementation to the ContextSource on startup, - instead of explicitly specifying the userDn and - password. The - AuthenticationSource will be queried by the - ContextSource for principal and credentials each - time an authenticated Context is to be - created. + + While the user name (i.e. user DN) and password used for + creating an authenticated Context are statically defined by + default - the ones defined in the context-source element + configuration will be used throughout the lifetime of the + ContextSource - there are several cases where this is not the desired behaviour. + A common scenario is that the principal and credentials of the current user should be used when + executing LDAP operations for that user. The default behaviour can be + modified by supplying a reference to an AuthenticationSource + implementation to the context-source element using the + authentication-source-ref element, + instead of explicitly specifying the username and + password. The + AuthenticationSource will be queried by the + ContextSource for principal and credentials each + time an authenticated Context is to be + created. + - If you are using Spring Security - you can make sure the principal and credentials of the currently logged in user - is used at all times by configuring your ContextSource - with an instance of the SpringSecurityAuthenticationSource - shipped with Spring Security. + + If you are using Spring Security + you can make sure the principal and credentials of the currently logged in user + is used at all times by configuring your ContextSource + with an instance of the SpringSecurityAuthenticationSource + shipped with Spring Security. + - The Spring bean definition for a - SpringSecurityAuthenticationSource + Using the <literal>SpringSecurityAuthenticationSource</literal> - <beans> - ... - <bean id="contextSource" class="org.springframework.ldap.core.support.LdapContextSource"> - <property name="url" value="ldap://localhost:389" /> - <property name="base" value="dc=example,dc=com" /> - <property name="authenticationSource" ref="springSecurityAuthenticationSource" /> - </bean> + +... + + +... +
]]> - We don't specify any userDn or - password to our ContextSource - when using an AuthenticationSource - these - properties are needed only when the default behaviour is - used. + + We don't specify any username or + password to our context-source + when using an AuthenticationSource - these + properties are needed only when the default behaviour is + used. + - When using the SpringSecurityAuthenticationSource - you need to use Spring Security's - LdapAuthenticationProvider to authenticate the - users against LDAP. + + When using the SpringSecurityAuthenticationSource + you need to use Spring Security's LdapAuthenticationProvider to authenticate the + users against LDAP. + - - - Default Authentication - - When using SpringSecurityAuthenticationSource, - authenticated contexts will only be possible to create once the user - is logged in using Spring Security. To use default authentication information - when no user is logged in, use the - DefaultValuesAuthenticationSourceDecorator: - - - Configuring a - DefaultValuesAuthenticationSourceDecorator - - <beans> - ... - <bean id="contextSource" class="org.springframework.ldap.core.support.LdapContextSource"> - <property name="url" value="ldap://localhost:389" /> - <property name="base" value="dc=example,dc=com" /> - <property name="authenticationSource" ref="authenticationSource" /> - </bean> - - <bean id="authenticationSource" - class="org.springframework.ldap.authentication.DefaultValuesAuthenticationSourceDecorator"> - <property name="target" ref="springSecurityAuthenticationSource" /> - <property name="defaultUser" value="cn=myDefaultUser" /> - <property name="defaultPassword" value="pass" /> - </bean> - - <bean id="springSecurityAuthenticationSource" - class="org.springframework.security.ldap.SpringSecurityAuthenticationSource" /> - ... -</beans> - - @@ -208,7 +365,7 @@ This LDAP connection pooling can be turned on/off using the pooled flag on AbstractContextSource. The default value is false (since release 1.3), i.e. the native - Java LDAP pooling will be turned on. The configuration of LDAP connection pooling is managed using + Java LDAP pooling will be turned off. The configuration of LDAP connection pooling is managed using System properties, so this needs to be handled manually, outside of the Spring Context configuration. Details of the native pooling configuration can be found here. @@ -226,64 +383,166 @@ Advanced ContextSource Configuration - - Alternate ContextFactory - - It is possible to configure the ContextFactory that the - ContextSource is to use when creating Contexts using the - contextFactory property. The default value is - com.sun.jndi.ldap.LdapCtxFactory. - - - Custom DirObjectFactory - - As described in , a DirObjectFactory - can be used to translate the Attributes of found Contexts - to a more useful DirContext implementation. This can be - configured using the dirObjectFactory property. You can use - this property if you have your own, custom DirObjectFactory implementation. - The default value is DefaultDirObjectFactory. - Custom DirContext Environment Properties - - In some cases the user might want to specify additional environment setup properties - in addition to the ones directly configurable from AbstractContextSource. - Such properties should be set in a Map and supplied to - the baseEnvironmentProperties property. + + In some cases the user might want to specify additional environment setup properties + in addition to the ones directly configurable on context-source. + Such properties should be set in a Map and referenced in + the base-env-props-ref attribute. LdapTemplate Configuration - - - Ignoring PartialResultExceptions - - Some Active Directory (AD) servers are unable to automatically following - referrals, which often leads to a PartialResultException being - thrown in searches. You can specify that PartialResultException - is to be ignored by setting the ignorePartialResultException - property to true. - This causes all referrals to be ignored, and no notice will be given that - a PartialResultException has been encountered. - There is currently no way of manually following referrals using LdapTemplate. - + + The LdapTemplate is defined using a <ldap:ldap-template> + tag. The simplest possible ldap-template declaration is the simple tag: + + Simplest possible ldap-template declaration + ]]> + + This will create an LdapTemplate instance with the default id, referencing the + default ContextSource, which is expected to have the id contextSource + (the default for the context-source element). + + + The configurable attributes on ldap-template are as follows: + + + LdapTemplate Configuration Attributes + + + + + + + Attribute + Default + Description + + + + + + id + + + ldapTemplate + + + The id of the created bean. + + + + + context-source-ref + + + contextSource + + + Id of the ContextSource instance to use. + + + + + count-limit + + + 0 + + + The default count limit for searches. 0 means no limit. + + + + + time-limit + + + 0 + + + The default time limit for searches in milliseconds. 0 means no limit. + + + + + search-scope + + + SUBTREE + + + The default search scope for searches. + Valid values are: + + OBJECT + ONELEVEL + SUBTREE + + + + + + ignore-name-not-found + + + false + + + Specifies whether NameNotFoundException should be ignored in searches. Setting this + attribute to true will cause errors caused by invalid search base to be silently swallowed. + + + + + ignore-partial-result + + + false + + + Specifies whether PartialResultException should be ignored in searches. Some LDAP servers + have problems with referrals; these should normally be followed automatically, but if this + doesn't work it will manifest itself with a PartialResultException. Setting this attribute + to true presents a work-around to this problem. + + + + + odm-ref + + + + + Id of the ObjectDirectoryMapper instance to use. Default is a default-configured DefaultObjectDirectoryMapper. + + + + +
+
+ Obtaining a reference to the base LDAP path - As described above, a base LDAP path may be supplied to the ContextSource, - specifying the root in the LDAP tree to which all operations will be relative. This means that - you will only be working with relative distinguished names throughout your system, which is - typically rather handy. There are however some cases in which you will need to have access - to the base path in order to be able to construct full DNs, relative to the actual root of the LDAP tree. - One example would be when working with LDAP groups (e.g. groupOfNames objectclass), - in which case each group member attribute value will need to be the full DN of the referenced member. - For that reason, Spring LDAP has a mechanism by which any Spring controlled bean may be supplied - the base path on startup. For beans to be notified of the base path, two things need to be in place: - First of all, the bean that wants the base path reference needs to implement the - BaseLdapNameAware interface. Secondly, a BaseLdapPathBeanPostProcessor - needs to be defined in the application context + + As described above, a base LDAP path may be supplied to the ContextSource, + specifying the root in the LDAP tree to which all operations will be relative. This means that + you will only be working with relative distinguished names throughout your system, which is + typically rather handy. There are however some cases in which you will need to have access + to the base path in order to be able to construct full DNs, relative to the actual root of the LDAP tree. + One example would be when working with LDAP groups (e.g. groupOfNames objectclass), + in which case each group member attribute value will need to be the full DN of the referenced member. + For that reason, Spring LDAP has a mechanism by which any Spring controlled bean may be supplied + the base path on startup. For beans to be notified of the base path, two things need to be in place: + First of all, the bean that wants the base path reference needs to implement the + BaseLdapNameAware interface. Secondly, a BaseLdapPathBeanPostProcessor + needs to be defined in the application context + Implementing <literal>BaseLdapNameAware</literal> package com.example.service; @@ -308,20 +567,21 @@ public class PersonService implements PersonService, BaseL <beans> ... - <bean id="contextSource" class="org.springframework.ldap.core.support.LdapContextSource"> - <property name="url" value="ldap://localhost:389" /> - <property name="base" value="dc=example,dc=com" /> - <property name="authenticationSource" ref="authenticationSource" /> - </bean> + <ldap:context-source + username="cn=Administrator" + password="secret" + url="ldap://localhost:389" + base="dc=261consulting,dc=com" /> ... <bean class="org.springframework.ldap.core.support.BaseLdapPathBeanPostProcessor" /> </beans> - The default behaviour of the BaseLdapPathBeanPostProcessor is to use the base path of the single - defined BaseLdapPathSource (AbstractContextSource )in the ApplicationContext. - If more than one BaseLdapPathSource is defined, you will need to specify which one to use with the - baseLdapPathSourceName property. + + The default behaviour of the BaseLdapPathBeanPostProcessor is to use the base path of the single + defined BaseLdapPathSource (AbstractContextSource)in the ApplicationContext. + If more than one BaseLdapPathSource is defined, you will need to specify which one to use with the + baseLdapPathSourceName property. diff --git a/src/docbkx/index.xml b/src/docbkx/index.xml index 6776b414..16d2bd1e 100644 --- a/src/docbkx/index.xml +++ b/src/docbkx/index.xml @@ -41,15 +41,15 @@ + + + - - - - + diff --git a/src/docbkx/odm.xml b/src/docbkx/odm.xml index b12665b9..d8524217 100644 --- a/src/docbkx/odm.xml +++ b/src/docbkx/odm.xml @@ -222,16 +222,12 @@ </property> </bean> -<bean id="ldapTemplate" - class="org.springframework.ldap.core.LdapTemplate"> - <property name="objectDirectoryMapper"> - <bean class="org.springframework.ldap.odm.impl.DefaultObjectDirectoryMapper"> - <property name="converterManager" ref="converterManager" /> - </bean> - </property> - <!-- More configuration of LdapTemplate here --> +<ldap:ldap-template id="ldapTemplate" odm-ref="odm" /> +<bean id="odm" class="org.springframework.ldap.odm.impl.DefaultObjectDirectoryMapper"> + <property name="converterManager" ref="converterManager" /> </bean> + diff --git a/src/docbkx/overview.xml b/src/docbkx/overview.xml index d22161af..211dd473 100644 --- a/src/docbkx/overview.xml +++ b/src/docbkx/overview.xml @@ -138,23 +138,32 @@ public class PersonDaoImpl implements PersonDao { defined in various ways, but the most common is through XML: - <beans> - <bean id="contextSource" class="org.springframework.ldap.core.support.LdapContextSource"> - <property name="url" value="ldap://localhost:389" /> - <property name="base" value="dc=example,dc=com" /> - <property name="userDn" value="cn=Manager" /> - <property name="password" value="secret" /> - </bean> + + - <bean id="ldapTemplate" class="org.springframework.ldap.core.LdapTemplate"> - <constructor-arg ref="contextSource" /> - </bean> + - <bean id="personDao" class="com.example.dao.PersonDaoImpl"> - <property name="ldapTemplate" ref="ldapTemplate" /> - </bean> -</beans> + + + + + + +]]> + + In order to use the custom XML namespace for configuring the Spring LDAP components + you need to include references to this namespace in your XML declaration as in the example above. + @@ -244,6 +253,15 @@ public class PersonDaoImpl implements PersonDao { lots of compilation warnings, and you are obviously encouraged to take appropriate action to get rid of these warning. + + The ODM (Object-Directory Mapping) functionality has been moved to core and there are new methods + in LdapOperations/LdapTemplate that uses this automatic + translation to/from ODM-annotated classes. See for more information. + + + A custom XML namespace is now provided to simplify configuration of Spring LDAP. + See for more information. + DistinguishedName and associated classes have been deprecated in favor of standard Java LdapName. See for information on how the library diff --git a/src/docbkx/pooling.xml b/src/docbkx/pooling.xml index 4c1cf110..6444d8b6 100644 --- a/src/docbkx/pooling.xml +++ b/src/docbkx/pooling.xml @@ -23,13 +23,10 @@ - Pooling support is provided by - PoolingContextSource - which can wrap any - ContextSource - and pool both read-only and read-write - DirContext - objects. + Pooling support is provided by supplying a <ldap:pooling /> sub-element + to the <ldap:context-source /> element in the application context configuration. + Read-only and read-write DirContext objects are pooled separately + (if anonymous-read-only is specified. Jakarta Commons-Pool @@ -47,17 +44,12 @@ DirContext connections to be checked to ensure they are still properly connected and configured when checking them out of the pool, - in to the pool or while idle in the pool + in to the pool or while idle in the pool. - The - DirContextValidator - interface is used by the - PoolingContextSource - for validation and - DefaultDirContextValidator - is provided as the default validation implementation. + If connection validation is configured, pooled connections are validated using + DefaultDirContextValidator. DefaultDirContextValidator does a @@ -75,12 +67,11 @@ passes validation, if no results are returned or an exception is thrown the DirContext - fails validation. The - DefaultDirContextValidator + fails validation. The default settings should work with no configuration changes on most LDAP servers and provide the fastest way to validate the - DirContext - . + DirContext. If customization required this can be done using the validation + configuration attributes, described below Connections will be automatically invalidated if they throw an exception that is considered @@ -94,21 +85,16 @@ - Pool Properties + Pool Configuration - The following properties are available on the - PoolingContextSource - for configuration of the DirContext pool. The - contextSource - property must be set and the - dirContextValidator - property must be set if validation is enabled, all other - properties are optional. + The following attributes are available on the + <ldap:pooling /> element + for configuration of the DirContext pool: - Pooling Configuration Properties + Pooling Configuration Attributes @@ -118,7 +104,7 @@ - Parameter + Attribute Default @@ -129,50 +115,7 @@ - contextSource - - - - null - - - - The - ContextSource - implementation to get - DirContext - s from to populate the pool. - - - - - - dirContextValidator - - - - null - - - - The - DirContextValidator - implementation to use when validating - connections. This is required if - testOnBorrow - , - testOnReturn - , or - testWhileIdle - options are set to - true - . - - - - - - maxActive + max-active @@ -189,7 +132,7 @@ - maxTotal + max-total @@ -206,7 +149,7 @@ - maxIdle + max-idle @@ -224,7 +167,7 @@ - minIdle + min-idle @@ -241,7 +184,7 @@ - maxWait + max-wait @@ -259,11 +202,11 @@ - whenExhaustedAction + when-exhausted - 1 (BLOCK) + BLOCK @@ -272,9 +215,7 @@ - The - FAIL (0) - option will throw a + The FAIL option will throw a NoSuchElementException @@ -284,29 +225,27 @@ - The - BLOCK (1) + The BLOCK option will wait until a new object is available. If - maxWait + max-wait is positive a NoSuchElementException is thrown if no new object is available after the - maxWait + max-wait time expires. - The - GROW (2) + The GROW option will create and return a new object (essentially making - maxActive + max-active meaningless). @@ -316,7 +255,7 @@ - testOnBorrow + test-on-borrow @@ -334,7 +273,7 @@ - testOnReturn + test-on-return @@ -349,7 +288,7 @@ - testWhileIdle + test-while-idle @@ -367,7 +306,7 @@ - timeBetweenEvictionRunsMillis + eviction-run-interval-millis @@ -385,7 +324,7 @@ - numTestsPerEvictionRun + tests-per-eviction-run @@ -402,7 +341,7 @@ - minEvictableIdleTimeMillis + min-evictable-time-millis @@ -420,7 +359,58 @@ - nonTransientExceptions + validation-query-base + + + + + LdapUtils.emptyName() + + + + The search base to be used when validating connections. Only used if + test-on-borrow, test-on-return, + or test-while-idle is specified + + + + + + validation-query-filter + + + + + objectclass=* + + + + The search filter to be used when validating connections. Only used if + test-on-borrow, test-on-return, + or test-while-idle is specified + + + + + + validation-query-search-controls-ref + + + + + null; default search control settings are described above. + + + + Id of a SearchControls instance to be used when validating connections. Only used if + test-on-borrow, test-on-return, + or test-while-idle is specified + + + + + + non-transient-exceptions @@ -429,8 +419,8 @@ - The Exceptions that should be considered non-transient with - regards to eager invalidation. Should any of the listed exceptions + Comma-separated list of Exception classes. The listed exceptions will be considered + non-transient with regards to eager invalidation. Should any of the listed exceptions (or subclasses of them) be thrown by a call to a pooled DirContext instance, that object will be automatically invalidated without any additional testOnReturn operation. @@ -456,17 +446,10 @@ ... - - - - - - - - - - - + + + ... ]]> @@ -474,37 +457,6 @@ In a real world example you would probably configure the pool options and enable connection validation; the above serves as an example to demonstrate the general idea. - - - Ensure that the - pooled - property is set to - false - on any - ContextSource - that will be wrapped in a - PoolingContextSource - . The - PoolingContextSource - must be able to create new connections when needed - and if - pooled - is set to - true - that may not be possible. - - - - - You'll notice that the actual - ContextSource - gets an id with a "Target" suffix. The bean you will - actually refer to is the - PoolingContextSource - that wraps the target - contextSource - - @@ -520,23 +472,12 @@ ... - - - - - - - - - - - - - - - - + + + ... ]]> @@ -544,8 +485,7 @@ The above example will test each DirContext before it is passed to the client application and test - DirContext - s that have been sitting idle in the pool. + DirContexts that have been sitting idle in the pool. diff --git a/src/docbkx/transactions.xml b/src/docbkx/transactions.xml index 8beeb459..d32c7748 100644 --- a/src/docbkx/transactions.xml +++ b/src/docbkx/transactions.xml @@ -43,74 +43,83 @@ Configuring Spring LDAP transactions should look very familiar if you're used to configuring Spring transactions. You will annotate your transacted classes with @Transactional, create a TransactionManager instance and include a <tx:annotation-driven> - tag in your bean configuraion. In addition to this, you will also need to wrap your ContextSource - in a TransactionAwareContextSourceProxy. + tag in your bean configuraion. - <beans> + +<beans> ... - <bean id="contextSourceTarget" class="org.springframework.ldap.core.support.LdapContextSource"> - <property name="url" value="ldap://localhost:389" /> - <property name="base" value="dc=example,dc=com" /> - <property name="userDn" value="cn=Manager" /> - <property name="password" value="secret" /> - </bean> + <ldap:context-source + url="ldap://localhost:389" + base="dc=example,dc=com" + username="cn=Manager" + password="secret" /> - <bean id="contextSource" - class="org.springframework.ldap.transaction.compensating.manager.TransactionAwareContextSourceProxy"> - <constructor-arg ref="contextSourceTarget" /> - </bean> - - <bean id="ldapTemplate" class="org.springframework.ldap.core.LdapTemplate"> - <constructor-arg ref="contextSource" /> - </bean> - - <bean id="transactionManager" - class="org.springframework.ldap.transaction.compensating.manager.ContextSourceTransactionManager"> - <property name="contextSource" ref="contextSource" /> - <property name="renamingStrategy"> + <ldap:ldap-template id="ldapTemplate" /> + <ldap:transaction-manager> <!-- - Note this default configuration will not work for more complex scenarios, see below for more information on RenamingStrategies. + Note this default configuration will not work for more complex scenarios, see below for more information on RenamingStrategies. --> - <bean class="org.springframework.ldap.transaction.compensating.support.DefaultTempEntryRenamingStrategy" /> - </property> - - </bean> + <ldap:default-renaming-strategy /> + </ldap:transaction-manager> + <!-- + The MyDataAccessObject class is annotated with @Transactional. + --> <bean id="myDataAccessObject" class="com.example.MyDataAccessObject"> <property name="ldapTemplate" ref="ldapTemplate" /> </bean> <tx:annotation-driven /> - ... - While the this setup will work fine for most simple use cases, some more complex scenarios will + While this setup will work fine for most simple use cases, some more complex scenarios will require additional configuration; more specifically if you will be creating or deleting subtrees within transactions, you will need to use an alternative TempEntryRenamingStrategy, as described in below In a real world example you would probably apply the transactions on the service object level rather than the DAO level; the above serves as an example to demonstrate the general idea. - You'll notice that the actual ContextSource instance gets an id with a - "Target" suffix. The bean you will actually refer to is the Proxy that are created - around the target; contextSource. JDBC Transaction Integration - A common use case when working against LDAP is that some of the data is stored in the LDAP tree, but - other data is stored in a relational database. In this case, transaction support becomes even more important, - since the update of the different resources should be synchronized. - While actual XA transactions is not supported, support is provided to conceptually wrap JDBC and LDAP - access within the same transaction using the ContextSourceAndDataSourceTransactionManager. - A DataSource and a ContextSource is supplied to the - ContextSourceAndDataSourceTransactionManager, which will then manage the two transactions, - virtually as if they were one. When performing a commit, the LDAP part of the operation will always - be performed first, allowing both transactions to be rolled back should the LDAP commit fail. The JDBC - part of the transaction is managed exactly as in DataSourceTransactionManager, except that - nested transactions is not supported. - Once again it should be noted that the provided support is all client side. The wrapped transaction is not - an XA transaction. No two-phase as such commit is performed, as the LDAP server will be unable to vote on its outcome. - Once again, however, for the majority of cases the supplied support will be sufficient. + + A common use case when working against LDAP is that some of the data is stored in the LDAP tree, but + other data is stored in a relational database. In this case, transaction support becomes even more important, + since the update of the different resources should be synchronized. + + + While actual XA transactions is not supported, support is provided to conceptually wrap JDBC and LDAP + access within the same transaction by supplying a data-source-ref attribute to the + <ldap:transaction-manager> tag. + This will create a ContextSourceAndDataSourceTransactionManager, + which will then manage the two transactions, virtually as if they were one. When performing a commit, + the LDAP part of the operation will always be performed first, allowing both transactions to be rolled + back should the LDAP commit fail. The JDBC part of the transaction is managed exactly as in + DataSourceTransactionManager, except that nested transactions is not supported: + + + <ldap:transaction-manager data-source-ref="dataSource" > + <ldap:default-renaming-strategy /> + <ldap:transaction-manager /> + + + + Once again it should be noted that the provided support is all client side. The wrapped transaction is not + an XA transaction. No two-phase as such commit is performed, as the LDAP server will be unable to vote on its outcome. + Once again, however, for the majority of cases the supplied support will be sufficient. + + + + The same thing can be accomplished for Hibernate integration by supplying a session-factory-ref + attribute to the <ldap:transaction-manager> tag. + + + <ldap:transaction-manager session-factory-ref="dataSource" > + <ldap:default-renaming-strategy /> + <ldap:transaction-manager /> + + + LDAP Compensating Transactions Explained @@ -189,23 +198,35 @@ javadocs. Renaming Strategies - As described in the table above, the transaction management of some operations require the original entry affected - by the operation to be temporarily renamed before the actual modification can be made in the commit. - The manner in which the temporary DN of the entry is calculated is managed by a TempEntryRenamingStrategy - supplied to the ContextSourceTransactionManager. Two implementations are supplied with Spring LDAP, - but if specific behaviour is required a custom implementation can easily be implemented by the user. The - provided TempEntryRenamingStrategy implementations are: + + As described in the table above, the transaction management of some operations require the original entry affected + by the operation to be temporarily renamed before the actual modification can be made in the commit. + The manner in which the temporary DN of the entry is calculated is managed by a TempEntryRenamingStrategy + specified in a sub-element to the <ldap:transaction-manager > declaration + in the configuration. Two implementations are supplied with Spring LDAP: + - DefaultTempEntryRenamingStrategy (the default). Adds a suffix to the least significant - part of the entry DN. E.g. for the DN cn=john doe, ou=users, this strategy would return the - temporary DN cn=john doe_temp, ou=users. The suffix is configurable using the tempSuffix - property - DifferentSubtreeTempEntryRenamingStrategy. Takes the least significant part of the DN - and appends a subtree DN to this. This makes all temporary entries be placed at a specific location in the LDAP tree. - The temporary subtree DN is configured using the subtreeNode property. E.g., if - subtreeNode is ou=tempEntries and the original DN of the entry is - cn=john doe, ou=users, the temporary DN will be cn=john doe, ou=tempEntries. - Note that the configured subtree node needs to be present in the LDAP tree. + + + DefaultTempEntryRenamingStrategy (the default). Specified using a + <ldap:default-renaming-strategy /> element. Adds a suffix to the least significant + part of the entry DN. E.g. for the DN cn=john doe, ou=users, this strategy would return the + temporary DN cn=john doe_temp, ou=users. + The suffix is configurable using the temp-suffix attribute. + + + + + DifferentSubtreeTempEntryRenamingStrategy. Specified using a + <ldap:different-subtree-renaming-strategy /> element. + Takes the least significant part of the DN and appends a subtree DN to this. + This makes all temporary entries be placed at a specific location in the LDAP tree. + The temporary subtree DN is configured using the subtree-node attribute. E.g., if + subtree-node is ou=tempEntries and the original DN of the entry is + cn=john doe, ou=users, the temporary DN will be cn=john doe, ou=tempEntries. + Note that the configured subtree node needs to be present in the LDAP tree. + + There are some situations where the DefaultTempEntryRenamingStrategy will not work. E.g. if your are planning