Interation tests of bind and rebind operations, along with bugfixes for found problems.

This commit is contained in:
Mattias Arthursson
2006-12-27 20:10:36 +00:00
parent 223dbbd2c2
commit 25cd9ed71b
18 changed files with 535 additions and 43 deletions

View File

@@ -43,6 +43,10 @@
<classpathentry kind="lib" path="lib/test/junit-addons.jar"/>
<classpathentry kind="lib" path="lib/test/DDSteps.jar"/>
<classpathentry kind="lib" path="lib/global/ldapbp.jar"/>
<classpathentry kind="lib" path="lib/buildtime/spring-jdbc.jar" sourcepath="lib/source/spring-src.zip"/>
<classpathentry kind="lib" path="lib/buildtime/spring-jpa.jar" sourcepath="lib/source/spring-src.zip"/>
<classpathentry kind="lib" path="lib/test/spring-aop.jar" sourcepath="lib/source/spring-src.zip"/>
<classpathentry kind="lib" path="lib/test/aopalliance.jar"/>
<classpathentry combineaccessrules="false" kind="src" path="/spring-ldap"/>
<classpathentry kind="output" path="target/classes"/>
</classpath>

View File

@@ -1,7 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<beansProjectDescription>
<configExtensions>
<configExtension>xml</configExtension>
</configExtensions>
<configs>
<config>src/iutest/conf/ldapTemplateBaseSuffixTestContext.xml</config>
<config>src/itest/java/conf/ldapTemplateTransactionTestContext.xml</config>
<config>src/iutest/conf/ldapTemplateTestContext.xml</config>
</configs>
<configSets>
@@ -21,5 +25,13 @@
<config>src/iutest/conf/ldapTemplateTestContext.xml</config>
</configs>
</configSet>
<configSet>
<name>iutesttransactions</name>
<allowBeanDefinitionOverriding>false</allowBeanDefinitionOverriding>
<incomplete>false</incomplete>
<configs>
<config>src/itest/java/conf/ldapTemplateTransactionTestContext.xml</config>
</configs>
</configSet>
</configSets>
</beansProjectDescription>

View File

@@ -43,6 +43,8 @@
conf="buildtime->default" />
<dependency org="org.springframework" name="spring-support" rev="2.0"
conf="buildtime->default" />
<dependency org="org.springframework" name="spring-jpa" rev="2.0"
conf="buildtime->default" />
<!-- test time only dependencies -->
<dependency org="com.cenqua.clover" name="clover" rev="1.3.12" conf="test->default" />
@@ -57,6 +59,8 @@
<dependency org="jexcelapi" name="jxl" rev="2.5.9" conf="test->default" />
<dependency org="org.ddsteps" name="DDSteps" rev="1.1" conf="test->default" />
<dependency org="cglib" name="cglib-nodep" rev="2.1_3" conf="test->default"/>
<dependency org="org.springframework" name="spring-aop" rev="2.0" conf="test->default"/>
<dependency org="aopalliance" name="aopalliance" rev="1.0" conf="test->default" />
<!-- Test dependencies for Apache Directory Server -->
<dependency org="org.apache.directory.server" name="apacheds-core" rev="1.0-RC3"

View File

@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans>
<import resource="classpath:/conf/apacheDsContext.xml" />
<bean id="placeholderConfig"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location"
value="classpath:/conf/ldap.properties" />
</bean>
<bean id="contextSourceTarget"
class="org.springframework.ldap.support.LdapContextSource">
<property name="urls" value="${urls}" />
<property name="userName" value="${userName}" />
<property name="password" value="${password}" />
<property name="base" value="${base}" />
<property name="pooled" value="false" />
<property name="dirObjectFactory"
value="org.springframework.ldap.support.DefaultDirObjectFactory" />
</bean>
<bean id="contextSource"
class="org.springframework.ldap.support.transaction.TransactionAwareContextSourceProxy">
<constructor-arg ref="contextSourceTarget" />
</bean>
<bean id="ldapTemplate"
class="org.springframework.ldap.LdapTemplate">
<constructor-arg ref="contextSource" />
</bean>
<bean id="transactionManager"
class="org.springframework.ldap.support.transaction.ContextSourceTransactionManager">
<property name="contextSource" ref="contextSourceTarget" />
</bean>
<bean name="dummyDaoTarget"
class="org.springframework.ldap.support.transaction.DummyDaoImpl">
<property name="ldapTemplate" ref="ldapTemplate" />
</bean>
<bean name="dummyDao"
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="transactionManager" ref="transactionManager" />
<property name="target" ref="dummyDaoTarget" />
<property name="transactionAttributes">
<props>
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
</beans>

View File

@@ -5,5 +5,4 @@ log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n
#Enable debug logging
log4j.category.net.sf.ldaptemplate=INFO
log4j.category.net.sf.ldaptemplate.LdapTemplate=DEBUG
log4j.category.org.springframework.ldap=DEBUG

View File

@@ -0,0 +1,58 @@
/*
* Copyright 2002-2005 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;
import java.io.File;
import java.util.Hashtable;
import javax.naming.Context;
import org.apache.directory.server.configuration.MutableServerStartupConfiguration;
import org.apache.directory.server.jndi.ServerContextFactory;
import org.springframework.beans.factory.InitializingBean;
/**
* Helper class to simplify Spring configuration of ApacheDS.
*
* @author Mattias Arthursson
*/
public class ConfigEnvHelper implements InitializingBean {
private final MutableServerStartupConfiguration configuration;
private final Hashtable initialEnv;
private final File workingDir = new File(System
.getProperty("java.io.tmpdir")
+ File.separator + "ldaptemplate_apacheds");
public ConfigEnvHelper(Hashtable initialEnv,
MutableServerStartupConfiguration configuration) {
this.initialEnv = initialEnv;
this.configuration = configuration;
}
public Hashtable getEnv() {
return initialEnv;
}
public void afterPropertiesSet() throws Exception {
initialEnv.put(Context.INITIAL_CONTEXT_FACTORY, ServerContextFactory.class.getName());
configuration.setWorkingDirectory(workingDir);
initialEnv.putAll(configuration.toJndiEnvironment());
}
}

View File

@@ -0,0 +1,125 @@
/*
* Copyright 2002-2005 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;
import java.util.Properties;
import javax.naming.Binding;
import javax.naming.Context;
import javax.naming.ContextNotEmptyException;
import javax.naming.InitialContext;
import javax.naming.Name;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.directory.server.core.configuration.ShutdownConfiguration;
import org.apache.directory.server.jndi.ServerContextFactory;
import org.apache.directory.server.protocol.shared.store.LdifFileLoader;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.ldap.support.DefaultDirObjectFactory;
import org.springframework.ldap.support.DistinguishedName;
/**
* Utility class to initialize the apache directory server for use in the
* integration tests.
*
* @author Mattias Arthursson
*
*/
public class LdapServerManager implements DisposableBean {
private static Log log = LogFactory.getLog(LdapServerManager.class);
private ContextSource contextSource;
public void setContextSource(ContextSource contextSource) {
this.contextSource = contextSource;
}
public void destroy() throws Exception {
Properties env = new Properties();
env.setProperty(Context.INITIAL_CONTEXT_FACTORY,
ServerContextFactory.class.getName());
env.setProperty(Context.SECURITY_AUTHENTICATION, "simple");
env.setProperty(Context.SECURITY_PRINCIPAL, "uid=admin,ou=system");
env.setProperty(Context.SECURITY_CREDENTIALS, "secret");
ShutdownConfiguration configuration = new ShutdownConfiguration();
env.putAll(configuration.toJndiEnvironment());
new InitialContext(env);
}
public void cleanAndSetup(String ldifFile) throws Exception {
DirContext ctx = contextSource.getReadWriteContext();
// First of all, make sure the database is empty.
Name startingPoint = null;
// Different test cases have different base paths. This means that the
// starting point will be different.
if (ctx.getEnvironment().get(
DefaultDirObjectFactory.JNDI_ENV_BASE_PATH_KEY) != null) {
startingPoint = DistinguishedName.EMPTY_PATH;
} else {
startingPoint = new DistinguishedName("dc=jayway,dc=se");
}
try {
log.info("Cleaning all present data.");
clearSubContexts(ctx, startingPoint);
// Load the ldif to the recently started server
log.info("Loading setup data");
LdifFileLoader loader = new LdifFileLoader(ctx, ldifFile);
loader.execute();
} finally {
ctx.close();
}
}
private void clearSubContexts(DirContext ctx, Name name)
throws NamingException {
NamingEnumeration enumeration = null;
try {
enumeration = ctx.listBindings(name);
while (enumeration.hasMore()) {
Binding element = (Binding) enumeration.next();
DistinguishedName childName = new DistinguishedName(element
.getName());
childName.prepend((DistinguishedName) name);
try {
ctx.destroySubcontext(childName);
} catch (ContextNotEmptyException e) {
clearSubContexts(ctx, childName);
ctx.destroySubcontext(childName);
}
}
} catch (NamingException e) {
e.printStackTrace();
} finally {
try {
enumeration.close();
} catch (Exception e) {
// Never mind this
}
}
}
}

View File

@@ -0,0 +1,119 @@
package org.springframework.ldap.support.transaction;
import javax.naming.NamingException;
import javax.naming.directory.Attributes;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.ldap.AttributesMapper;
import org.springframework.ldap.EntryNotFoundException;
import org.springframework.ldap.LdapServerManager;
import org.springframework.ldap.LdapTemplate;
import org.springframework.test.AbstractDependencyInjectionSpringContextTests;
public class ContextSourceTransactionManagerIntegrationTest extends
AbstractDependencyInjectionSpringContextTests {
private static Log log = LogFactory
.getLog(ContextSourceTransactionManagerIntegrationTest.class);
public ContextSourceTransactionManagerIntegrationTest() {
setAutowireMode(AbstractDependencyInjectionSpringContextTests.AUTOWIRE_BY_NAME);
}
private DummyDaoImpl dummyDao;
private LdapTemplate ldapTemplate;
private LdapServerManager ldapServerManager;
public void setLdapServerManager(LdapServerManager ldapServerManager) {
this.ldapServerManager = ldapServerManager;
}
public void setLdapTemplate(LdapTemplate ldapTemplate) {
this.ldapTemplate = ldapTemplate;
}
public void setDummyDao(DummyDaoImpl dummyDaoImpl) {
this.dummyDao = dummyDaoImpl;
}
protected String[] getConfigLocations() {
return new String[] { "conf/ldapTemplateTransactionTestContext.xml" };
}
protected void onSetUp() throws Exception {
ldapServerManager.cleanAndSetup("setup_data.ldif");
}
public void testCreateWithException() {
try {
dummyDao.createWithException("Sweden", "company1",
"some testperson", "testperson", "some description");
fail("RuntimeException expected");
} catch (RuntimeException expected) {
assertTrue(true);
}
log.debug("Verifying result");
try {
ldapTemplate.lookup("cn=some testperson, ou=company1, c=Sweden");
fail("EntryNotFoundException expected");
} catch (EntryNotFoundException expected) {
assertTrue(true);
}
}
public void testCreateWithNoException() {
dummyDao.create("Sweden", "company1", "some testperson", "testperson",
"some description");
log.debug("Verifying result");
ldapTemplate.lookup("cn=some testperson, ou=company1, c=Sweden");
}
public void testUpdateWithException() {
String dn = "cn=Some Person,ou=company1,c=Sweden";
try {
dummyDao.updateWithException(dn, "Updated Person",
"Updated description");
fail("RuntimeException expected");
} catch (RuntimeException expected) {
assertTrue(true);
}
log.debug("Verifying result");
Object result = ldapTemplate.lookup(dn, new AttributesMapper() {
public Object mapFromAttributes(Attributes attributes)
throws NamingException {
assertEquals("Person", attributes.get("sn").get());
assertEquals("Sweden, Company1, Some Person", attributes.get(
"description").get());
return new Object();
}
});
assertNotNull(result);
}
public void testUpdate() {
String dn = "cn=Some Person,ou=company1,c=Sweden";
dummyDao.update(dn, "Updated Person", "Updated description");
log.debug("Verifying result");
Object result = ldapTemplate.lookup(dn, new AttributesMapper() {
public Object mapFromAttributes(Attributes attributes)
throws NamingException {
assertEquals("Updated Person", attributes.get("sn").get());
assertEquals("Updated description", attributes.get(
"description").get());
return new Object();
}
});
assertNotNull(result);
}
}

View File

@@ -0,0 +1,4 @@
package org.springframework.ldap.support.transaction;
public interface DummyDao {
}

View File

@@ -0,0 +1,49 @@
package org.springframework.ldap.support.transaction;
import org.springframework.ldap.LdapTemplate;
import org.springframework.ldap.support.DirContextAdapter;
import org.springframework.ldap.support.DistinguishedName;
public class DummyDaoImpl {
private LdapTemplate ldapTemplate;
public void setLdapTemplate(LdapTemplate ldapTemplate) {
this.ldapTemplate = ldapTemplate;
}
public void createWithException(String country, String company,
String fullname, String lastname, String description) {
create(country, company, fullname, lastname, description);
throw new RuntimeException("This method failed");
}
public void create(String country, String company, String fullname,
String lastname, String description) {
DistinguishedName dn = new DistinguishedName();
dn.add("c", country);
dn.add("ou", company);
dn.add("cn", fullname);
DirContextAdapter ctx = new DirContextAdapter();
ctx.setAttributeValues("objectclass", new String[] { "top", "person" });
ctx.setAttributeValue("cn", fullname);
ctx.setAttributeValue("sn", lastname);
ctx.setAttributeValue("description", description);
ldapTemplate.bind(dn, ctx, null);
}
public void update(String dn, String lastname, String description) {
DirContextAdapter ctx = (DirContextAdapter) ldapTemplate.lookup(dn);
ctx.setAttributeValue("sn", lastname);
ctx.setAttributeValue("description", description);
ctx.update();
ldapTemplate.rebind(dn, ctx, null);
}
public void updateWithException(String dn, String lastname,
String description) {
update(dn, lastname, description);
throw new RuntimeException("This method failed.");
}
}

View File

@@ -0,0 +1,9 @@
package org.springframework.ldap.support.transaction;
public class DummyServiceImpl {
private DummyDaoImpl dummyDaoImpl;
public void setDummyDaoImpl(DummyDaoImpl dummyDaoImpl) {
this.dummyDaoImpl = dummyDaoImpl;
}
}

View File

@@ -3,6 +3,8 @@ package org.springframework.ldap.support.transaction;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.ldap.ContextSource;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionException;
@@ -20,6 +22,9 @@ import org.springframework.transaction.support.TransactionSynchronizationManager
public class ContextSourceTransactionManager extends
AbstractPlatformTransactionManager {
private static Log log = LogFactory
.getLog(ContextSourceTransactionManager.class);
private ContextSource contextSource;
/**
@@ -98,11 +103,14 @@ public class ContextSourceTransactionManager extends
* @see org.springframework.transaction.support.AbstractPlatformTransactionManager#doCleanupAfterCompletion(java.lang.Object)
*/
protected void doCleanupAfterCompletion(Object transaction) {
ContextSourceTransactionObject txObject = (ContextSourceTransactionObject) transaction;
log.debug("Cleaning stored ContextHolder");
TransactionSynchronizationManager.unbindResource(contextSource);
ContextSourceTransactionObject txObject = (ContextSourceTransactionObject) transaction;
DirContext ctx = txObject.getContextHolder().getCtx();
try {
log.debug("Closing target context");
ctx.close();
} catch (NamingException e) {
e.printStackTrace();
@@ -110,5 +118,4 @@ public class ContextSourceTransactionManager extends
txObject.getContextHolder().clear();
}
}

View File

@@ -2,6 +2,6 @@ package org.springframework.ldap.support.transaction;
import javax.naming.directory.DirContext;
public interface DirContextProxy {
public interface DirContextProxy extends DirContext {
DirContext getTargetContext();
}

View File

@@ -1,5 +1,9 @@
package org.springframework.ldap.support.transaction;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Stack;
import javax.naming.directory.DirContext;
@@ -42,6 +46,7 @@ public class LdapCompensatingTransactionDataManager implements
* @see org.springframework.ldap.support.CompensatingTransactionDataManager#rollback()
*/
public void rollback() {
log.debug("Performing rollback");
while (!rollbackOperations.isEmpty()) {
CompensatingTransactionRollbackOperation rollbackOperation = (CompensatingTransactionRollbackOperation) rollbackOperations
.pop();
@@ -83,23 +88,6 @@ public class LdapCompensatingTransactionDataManager implements
return new NullRecordingOperation();
}
static class SingleContextSource implements ContextSource {
private DirContext ctx;
public SingleContextSource(DirContext ctx) {
this.ctx = ctx;
}
public DirContext getReadOnlyContext() throws DataAccessException {
return ctx;
}
public DirContext getReadWriteContext() throws DataAccessException {
return ctx;
}
}
/**
* Set the LdapOperations to use. For testing purposes only.
*
@@ -110,4 +98,61 @@ public class LdapCompensatingTransactionDataManager implements
this.ldapOperations = ldapOperations;
}
static class SingleContextSource implements ContextSource {
private DirContext ctx;
public SingleContextSource(DirContext ctx) {
this.ctx = ctx;
}
public DirContext getReadOnlyContext() throws DataAccessException {
return getNonClosingDirContextProxy(ctx);
}
public DirContext getReadWriteContext() throws DataAccessException {
return getNonClosingDirContextProxy(ctx);
}
private DirContext getNonClosingDirContextProxy(DirContext context) {
return (DirContext) Proxy.newProxyInstance(DirContextProxy.class
.getClassLoader(), new Class[] { DirContextProxy.class },
new NonClosingDirContextInvocationHandler(context));
}
}
public static class NonClosingDirContextInvocationHandler implements
InvocationHandler {
private DirContext target;
public NonClosingDirContextInvocationHandler(DirContext target) {
this.target = target;
}
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("equals")) {
// Only consider equal when proxies are identical.
return (proxy == args[0] ? Boolean.TRUE : Boolean.FALSE);
} else if (methodName.equals("hashCode")) {
// Use hashCode of Connection proxy.
return new Integer(proxy.hashCode());
} else if (methodName.equals("close")) {
// Never close the target context, as this class will only be
// used for operations concerning the compensating transactions.
return null;
}
try {
return method.invoke(target, args);
} catch (InvocationTargetException e) {
throw e.getTargetException();
}
}
}
}

View File

@@ -5,6 +5,8 @@ import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.ldap.ContextSource;
import org.springframework.ldap.support.DistinguishedName;
import org.springframework.transaction.support.TransactionSynchronizationManager;
@@ -16,6 +18,8 @@ import org.springframework.util.Assert;
* @author Mattias Arthursson
*/
public class LdapUtils {
private static Log log = LogFactory.getLog(LdapUtils.class);
public static final String REBIND_METHOD_NAME = "rebind";
public static final String BIND_METHOD_NAME = "bind";
@@ -63,9 +67,12 @@ public class LdapUtils {
.getResource(contextSource);
if (transactionContextHolder == null
|| transactionContextHolder.getCtx() != context) {
log.debug("Closing context");
// This is not the transactional context or the transaction is
// no longer active - we should close it.
context.close();
} else {
log.debug("Leaving transactional context open");
}
}

View File

@@ -17,21 +17,7 @@ public class TransactionAwareContextSourceProxy implements ContextSource {
}
public DirContext getReadOnlyContext() throws DataAccessException {
DirContextHolder contextHolder = (DirContextHolder) TransactionSynchronizationManager
.getResource(target);
DirContext ctx = null;
if (contextHolder != null) {
ctx = contextHolder.getCtx();
}
if (ctx == null) {
ctx = target.getReadOnlyContext();
if (contextHolder != null) {
contextHolder.setCtx(ctx);
}
}
return getTransactionAwareDirContextProxy(ctx, target);
return getReadWriteContext();
}
private DirContext getTransactionAwareDirContextProxy(DirContext context,
@@ -45,6 +31,20 @@ public class TransactionAwareContextSourceProxy implements ContextSource {
}
public DirContext getReadWriteContext() throws DataAccessException {
throw new UnsupportedOperationException("Not implemented yet");
DirContextHolder contextHolder = (DirContextHolder) TransactionSynchronizationManager
.getResource(target);
DirContext ctx = null;
if (contextHolder != null) {
ctx = contextHolder.getCtx();
}
if (ctx == null) {
ctx = target.getReadWriteContext();
if (contextHolder != null) {
contextHolder.setCtx(ctx);
}
}
return getTransactionAwareDirContextProxy(ctx, target);
}
}

View File

@@ -1,6 +1,3 @@
/**
*
*/
package org.springframework.ldap.support.transaction;
import java.lang.reflect.InvocationHandler;
@@ -45,7 +42,8 @@ public class TransactionAwareDirContextInvocationHandler implements
return null;
} else if (LdapUtils.isSupportedWriteTransactionOperation(methodName)) {
// Store transaction data and allow operation to proceed.
LdapUtils.storeCompensatingTransactionData(contextSource, methodName, args);
LdapUtils.storeCompensatingTransactionData(contextSource,
methodName, args);
}
try {
@@ -54,5 +52,4 @@ public class TransactionAwareDirContextInvocationHandler implements
throw e.getTargetException();
}
}
}