From c98110b8c2d68bd41c794c0bda601311dec2085d Mon Sep 17 00:00:00 2001 From: Mattias Hellborg Arthursson Date: Thu, 10 Oct 2013 13:32:45 +0200 Subject: [PATCH] LDAP-266: Initial effort for automatic repository support. --- core/build.gradle | 3 +- .../springframework/ldap/config/Elements.java | 1 + .../ldap/config/LdapNamespaceHandler.java | 6 + .../ldap/core/LdapOperations.java | 27 ++- .../ldap/core/LdapTemplate.java | 14 ++ .../ldap/odm/core/ObjectDirectoryMapper.java | 9 + .../impl/DefaultObjectDirectoryMapper.java | 16 +- .../ldap/query/LdapQueryBuilder.java | 15 +- .../repository/LdapRepositoryFactory.java | 55 +++++ .../repository/LdapRepositoryFactoryBean.java | 32 +++ .../ldap/repository/SimpleLdapRepository.java | 191 ++++++++++++++++++ .../LdapRepositoryConfigurationExtension.java | 53 +++++ .../ldap/config/spring-ldap-2.0.xsd | 22 +- .../ldap/config/DummyEntity.java | 7 + .../ldap/config/DummyLdapRepository.java | 27 +++ .../LdapTemplateNamespaceHandlerTest.java | 8 + ...dap-namespace-config-with-repositories.xml | 12 ++ gradle/java.gradle | 1 + .../ldap/itest/odm/Person.java | 17 +- .../itest/repositories/PersonRepository.java | 28 +++ .../itest/repository/RepositoryScanITest.java | 172 ++++++++++++++++ .../conf/repositoryScanTestContext.xml | 24 +++ 22 files changed, 732 insertions(+), 8 deletions(-) create mode 100644 core/src/main/java/org/springframework/ldap/repository/LdapRepositoryFactory.java create mode 100644 core/src/main/java/org/springframework/ldap/repository/LdapRepositoryFactoryBean.java create mode 100644 core/src/main/java/org/springframework/ldap/repository/SimpleLdapRepository.java create mode 100644 core/src/main/java/org/springframework/ldap/repository/config/LdapRepositoryConfigurationExtension.java create mode 100644 core/src/test/java/org/springframework/ldap/config/DummyEntity.java create mode 100644 core/src/test/java/org/springframework/ldap/config/DummyLdapRepository.java create mode 100644 core/src/test/resources/ldap-namespace-config-with-repositories.xml create mode 100644 test/integration-tests/src/main/java/org/springframework/ldap/itest/repositories/PersonRepository.java create mode 100644 test/integration-tests/src/test/java/org/springframework/ldap/itest/repository/RepositoryScanITest.java create mode 100644 test/integration-tests/src/test/resources/conf/repositoryScanTestContext.xml diff --git a/core/build.gradle b/core/build.gradle index da7a8879..0de33d5b 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -12,7 +12,8 @@ dependencies { compile "commons-logging:commons-logging:$commonsLoggingVersion", "org.springframework:spring-core:$springVersion", "org.springframework:spring-beans:$springVersion", - "org.springframework:spring-tx:$springVersion" + "org.springframework:spring-tx:$springVersion", + "org.springframework.data:spring-data-commons:$springDataVersion" provided "commons-pool:commons-pool:$commonsPoolVersion", "com.sun:ldapbp:1.0", diff --git a/core/src/main/java/org/springframework/ldap/config/Elements.java b/core/src/main/java/org/springframework/ldap/config/Elements.java index 5f490712..2c3d8817 100644 --- a/core/src/main/java/org/springframework/ldap/config/Elements.java +++ b/core/src/main/java/org/springframework/ldap/config/Elements.java @@ -24,6 +24,7 @@ public abstract class Elements { public static final String POOLING = "pooling"; public static final String LDAP_TEMPLATE = "ldap-template"; public static final String TRANSACTION_MANAGER = "transaction-manager"; + public static final String REPOSITORIES = "repositories"; public static final String DEFAULT_RENAMING_STRATEGY = "default-renaming-strategy"; public static final String DIFFERENT_SUBTREE_RENAMING_STRATEGY = "different-subtree-renaming-strategy"; } diff --git a/core/src/main/java/org/springframework/ldap/config/LdapNamespaceHandler.java b/core/src/main/java/org/springframework/ldap/config/LdapNamespaceHandler.java index 5bd9da1b..c215c3f4 100644 --- a/core/src/main/java/org/springframework/ldap/config/LdapNamespaceHandler.java +++ b/core/src/main/java/org/springframework/ldap/config/LdapNamespaceHandler.java @@ -17,6 +17,8 @@ package org.springframework.ldap.config; import org.springframework.beans.factory.xml.NamespaceHandlerSupport; +import org.springframework.data.repository.config.RepositoryBeanDefinitionParser; +import org.springframework.ldap.repository.config.LdapRepositoryConfigurationExtension; /** * @author Mattias Hellborg Arthursson @@ -24,8 +26,12 @@ import org.springframework.beans.factory.xml.NamespaceHandlerSupport; public class LdapNamespaceHandler extends NamespaceHandlerSupport { @Override public void init() { + LdapRepositoryConfigurationExtension extension = new LdapRepositoryConfigurationExtension(); + RepositoryBeanDefinitionParser repositoryParser = new RepositoryBeanDefinitionParser(extension); + registerBeanDefinitionParser(Elements.CONTEXT_SOURCE, new ContextSourceParser()); registerBeanDefinitionParser(Elements.LDAP_TEMPLATE, new LdapTemplateParser()); registerBeanDefinitionParser(Elements.TRANSACTION_MANAGER, new TransactionManagerParser()); + registerBeanDefinitionParser(Elements.REPOSITORIES, repositoryParser); } } diff --git a/core/src/main/java/org/springframework/ldap/core/LdapOperations.java b/core/src/main/java/org/springframework/ldap/core/LdapOperations.java index d3e7361b..2eb8b58f 100644 --- a/core/src/main/java/org/springframework/ldap/core/LdapOperations.java +++ b/core/src/main/java/org/springframework/ldap/core/LdapOperations.java @@ -21,6 +21,7 @@ import org.springframework.ldap.ContextNotEmptyException; import org.springframework.ldap.NamingException; import org.springframework.ldap.core.support.AbstractContextSource; import org.springframework.ldap.filter.Filter; +import org.springframework.ldap.odm.core.ObjectDirectoryMapper; import org.springframework.ldap.query.LdapQuery; import org.springframework.ldap.support.LdapUtils; @@ -1620,6 +1621,22 @@ public interface LdapOperations { */ T searchForObject(String base, String filter, ContextMapper mapper); + /** + * Perform a search with parameters from the specified LdapQuery. All found objects will be supplied to the + * NameClassPairCallbackHandler for processing. + * + * @param query the LDAP query specification. + * @param callbackHandler the NameClassPairCallbackHandler to supply all found entries to. + * @return a List containing all entries received from the + * ContextMapper. + * + * @throws NamingException if any error occurs. + * @since 2.0 + * @see org.springframework.ldap.query.LdapQueryBuilder + * @see org.springframework.ldap.core.support.CountNameClassPairCallbackHandler + */ + void search(LdapQuery query, NameClassPairCallbackHandler callbackHandler); + /** * Perform a search with parameters from the specified LdapQuery. All found objects will be supplied to the * ContextMapper for processing, and all returned objects will be collected in a list to be returned. @@ -1699,7 +1716,7 @@ public interface LdapOperations { * is set in the object, this will be used as the distinguished name of the new entry. If no explicit DN is specified, * an attempt will be made to calculate the name from fields annotated with {@link org.springframework.ldap.odm.annotations.DnAttribute}. * - * @param entry The entry to be create, it must not already exist in the directory. + * @param entry The entry to be create, it must not be null or already exist in the directory. * * @throws org.springframework.ldap.NamingException on error. * @throws IllegalArgumentException if the entry is null or on failure to determine the distinguished name. @@ -1825,4 +1842,12 @@ public interface LdapOperations { * @throws IncorrectResultSizeDataAccessException if more than one matching entry is found */ T findOne(LdapQuery query, Class clazz); + + /** + * Get the configured ObjectDirectoryMapper. For internal use. + * + * @return the configured ObjectDirectoryMapper. + * @since 2.0 + */ + ObjectDirectoryMapper getObjectDirectoryMapper(); } diff --git a/core/src/main/java/org/springframework/ldap/core/LdapTemplate.java b/core/src/main/java/org/springframework/ldap/core/LdapTemplate.java index 3a1bea93..af84a063 100644 --- a/core/src/main/java/org/springframework/ldap/core/LdapTemplate.java +++ b/core/src/main/java/org/springframework/ldap/core/LdapTemplate.java @@ -114,6 +114,11 @@ public class LdapTemplate implements LdapOperations, InitializingBean { this.contextSource = contextSource; } + @Override + public ObjectDirectoryMapper getObjectDirectoryMapper() { + return odm; + } + /** * Set the ObjectDirectoryMapper instance to use. * @@ -1669,6 +1674,15 @@ public class LdapTemplate implements LdapOperations, InitializingBean { } } + @Override + public void search(LdapQuery query, NameClassPairCallbackHandler callbackHandler) { + SearchControls searchControls = searchControlsForQuery(query, DONT_RETURN_OBJ_FLAG); + search(query.base(), + query.filter().encode(), + searchControls, + callbackHandler); + } + @Override public List search(LdapQuery query, ContextMapper mapper) { SearchControls searchControls = searchControlsForQuery(query, RETURN_OBJ_FLAG); diff --git a/core/src/main/java/org/springframework/ldap/odm/core/ObjectDirectoryMapper.java b/core/src/main/java/org/springframework/ldap/odm/core/ObjectDirectoryMapper.java index 2098ac09..26de34d6 100644 --- a/core/src/main/java/org/springframework/ldap/odm/core/ObjectDirectoryMapper.java +++ b/core/src/main/java/org/springframework/ldap/odm/core/ObjectDirectoryMapper.java @@ -56,6 +56,15 @@ public interface ObjectDirectoryMapper { */ Name getId(Object entry); + /** + * Set the distinguished name for the specified object. + * + * @param entry the entry to set the name on + * @param id the name to set + * @throws org.springframework.ldap.NamingException on error. + */ + void setId(Object entry, Name id); + Name getCalculatedId(Object entry); /** diff --git a/core/src/main/java/org/springframework/ldap/odm/core/impl/DefaultObjectDirectoryMapper.java b/core/src/main/java/org/springframework/ldap/odm/core/impl/DefaultObjectDirectoryMapper.java index 066b1aea..7105b0d7 100644 --- a/core/src/main/java/org/springframework/ldap/odm/core/impl/DefaultObjectDirectoryMapper.java +++ b/core/src/main/java/org/springframework/ldap/odm/core/impl/DefaultObjectDirectoryMapper.java @@ -367,13 +367,27 @@ public class DefaultObjectDirectoryMapper implements ObjectDirectoryMapper { @Override public Name getId(Object entry) { try { - return (Name)getEntityData(entry.getClass()).metaData.getIdAttribute().getField().get(entry); + return (Name) getIdField(entry).get(entry); } catch (Exception e) { throw new InvalidEntryException(String.format("Can't get Id field from Entry %1$s", entry), e); } } + private Field getIdField(Object entry) { + return getEntityData(entry.getClass()).metaData.getIdAttribute().getField(); + } + + @Override + public void setId(Object entry, Name id) { + try { + getIdField(entry).set(entry, id); + } catch (Exception e) { + throw new InvalidEntryException( + String.format("Can't set Id field on Entry %s to %s", entry, id), e); + } + } + @Override public Name getCalculatedId(Object entry) { Assert.notNull(entry, "Entry must not be null"); diff --git a/core/src/main/java/org/springframework/ldap/query/LdapQueryBuilder.java b/core/src/main/java/org/springframework/ldap/query/LdapQueryBuilder.java index dfa73e34..d78f4537 100644 --- a/core/src/main/java/org/springframework/ldap/query/LdapQueryBuilder.java +++ b/core/src/main/java/org/springframework/ldap/query/LdapQueryBuilder.java @@ -157,9 +157,13 @@ public class LdapQueryBuilder implements LdapQuery { * @throws IllegalStateException if a filter has already been specified. */ public ConditionCriteria where(String attribute) { + initRootContainer(); + return new DefaultConditionCriteria(rootContainer, attribute); + } + + private void initRootContainer() { assertFilterNotStarted(); rootContainer = new DefaultContainerCriteria(this); - return new DefaultConditionCriteria(rootContainer, attribute); } /** @@ -174,12 +178,17 @@ public class LdapQueryBuilder implements LdapQuery { * @throws IllegalStateException if a filter has already been specified. */ public LdapQuery filter(String hardcodedFilter) { - assertFilterNotStarted(); - rootContainer = new DefaultContainerCriteria(this); + initRootContainer(); rootContainer.append(new HardcodedFilter(hardcodedFilter)); return this; } + public LdapQuery filter(Filter filter) { + initRootContainer(); + rootContainer.append(filter); + return this; + } + /** * Specify a hardcoded filter using the specified parameters. The parameters will be properly encoded using * {@link LdapEncoder#filterEncode(String)} to make sure no malicious data gets through. The filterFormat diff --git a/core/src/main/java/org/springframework/ldap/repository/LdapRepositoryFactory.java b/core/src/main/java/org/springframework/ldap/repository/LdapRepositoryFactory.java new file mode 100644 index 00000000..cc540f6a --- /dev/null +++ b/core/src/main/java/org/springframework/ldap/repository/LdapRepositoryFactory.java @@ -0,0 +1,55 @@ +/* + * 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.repository; + +import org.springframework.data.repository.core.EntityInformation; +import org.springframework.data.repository.core.RepositoryMetadata; +import org.springframework.data.repository.core.support.RepositoryFactorySupport; +import org.springframework.ldap.core.LdapOperations; + +import java.io.Serializable; + +/** + * @author Mattias Hellborg Arthursson + * @since 2.0 + */ +public class LdapRepositoryFactory extends RepositoryFactorySupport { + private final LdapOperations ldapOperations; + + public LdapRepositoryFactory(LdapOperations ldapOperations) { + this.ldapOperations = ldapOperations; + } + + @Override + public EntityInformation getEntityInformation(Class domainClass) { + return null; + } + + @Override + @SuppressWarnings({"unchecked", "rawtypes"}) + protected Object getTargetRepository(RepositoryMetadata metadata) { + return new SimpleLdapRepository( + ldapOperations, + ldapOperations.getObjectDirectoryMapper(), + metadata.getDomainType()); + } + + @Override + protected Class getRepositoryBaseClass(RepositoryMetadata metadata) { + return SimpleLdapRepository.class; + } +} diff --git a/core/src/main/java/org/springframework/ldap/repository/LdapRepositoryFactoryBean.java b/core/src/main/java/org/springframework/ldap/repository/LdapRepositoryFactoryBean.java new file mode 100644 index 00000000..8600f4a2 --- /dev/null +++ b/core/src/main/java/org/springframework/ldap/repository/LdapRepositoryFactoryBean.java @@ -0,0 +1,32 @@ +package org.springframework.ldap.repository; + +import org.springframework.data.repository.Repository; +import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport; +import org.springframework.data.repository.core.support.RepositoryFactorySupport; +import org.springframework.ldap.core.LdapOperations; +import org.springframework.util.Assert; + +import javax.naming.Name; + +/** + * @author Mattias Hellborg Arthursson + * @since 2.0 + */ +public class LdapRepositoryFactoryBean, S> extends RepositoryFactoryBeanSupport { + private LdapOperations ldapOperations; + + public void setLdapOperations(LdapOperations ldapOperations) { + this.ldapOperations = ldapOperations; + } + + @Override + protected RepositoryFactorySupport createRepositoryFactory() { + return new LdapRepositoryFactory(ldapOperations); + } + + @Override + public void afterPropertiesSet() { + super.afterPropertiesSet(); + Assert.notNull(ldapOperations, "LdapOperations must be set"); + } +} diff --git a/core/src/main/java/org/springframework/ldap/repository/SimpleLdapRepository.java b/core/src/main/java/org/springframework/ldap/repository/SimpleLdapRepository.java new file mode 100644 index 00000000..d8e5e844 --- /dev/null +++ b/core/src/main/java/org/springframework/ldap/repository/SimpleLdapRepository.java @@ -0,0 +1,191 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.repository; + +import org.springframework.data.domain.Persistable; +import org.springframework.data.repository.CrudRepository; +import org.springframework.ldap.NameNotFoundException; +import org.springframework.ldap.core.LdapOperations; +import org.springframework.ldap.core.support.CountNameClassPairCallbackHandler; +import org.springframework.ldap.filter.Filter; +import org.springframework.ldap.odm.core.ObjectDirectoryMapper; +import org.springframework.ldap.query.LdapQuery; +import org.springframework.util.Assert; + +import javax.naming.Name; +import java.util.Iterator; + +import static org.springframework.ldap.query.LdapQueryBuilder.query; + +/** + * Base repository implementation for LDAP. + * + * @author Mattias Hellborg Arthursson + * @since 2.0 + */ +public class SimpleLdapRepository implements CrudRepository { + private static final String OBJECTCLASS_ATTRIBUTE = "objectclass"; + private final LdapOperations ldapOperations; + private final ObjectDirectoryMapper odm; + private final Class clazz; + + public SimpleLdapRepository(LdapOperations ldapOperations, ObjectDirectoryMapper odm, Class clazz) { + this.ldapOperations = ldapOperations; + this.odm = odm; + this.clazz = clazz; + } + + @Override + public long count() { + Filter filter = odm.filterFor(clazz, null); + CountNameClassPairCallbackHandler callback = new CountNameClassPairCallbackHandler(); + LdapQuery query = query().attributes(OBJECTCLASS_ATTRIBUTE).filter(filter); + ldapOperations.search(query, callback); + + return callback.getNoOfRows(); + } + + private boolean isNew(S entity, Name id) { + if (entity instanceof Persistable) { + Persistable persistable = (Persistable) entity; + return persistable.isNew(); + } else { + return id != null; + } + } + + @Override + public S save(S entity) { + Assert.notNull(entity, "Entity must not be null"); + Name declaredId = odm.getId(entity); + Name calculatedId = odm.getCalculatedId(entity); + + if(isNew(entity, declaredId)) { + if(declaredId == null) { + odm.setId(entity, calculatedId); + } + + ldapOperations.create(entity); + } else { + ldapOperations.update(entity); + if(declaredId != calculatedId) { + odm.setId(entity, calculatedId); + } + } + + return entity; + } + + @Override + public Iterable save(Iterable entities) { + return new DelegatingIterable(entities, new Function() { + @Override + public S transform(S entry) { + return save(entry); + } + }); + } + + @Override + public T findOne(Name name) { + Assert.notNull(name, "Id must not be null"); + try { + return ldapOperations.findByDn(name, clazz); + } catch (NameNotFoundException e) { + return null; + } + } + + @Override + public boolean exists(Name name) { + return findOne(name) != null; + } + + @Override + public Iterable findAll() { + return ldapOperations.findAll(clazz); + } + + @Override + public Iterable findAll(final Iterable names) { + return new DelegatingIterable(names, new Function() { + @Override + public T transform(Name name) { + return findOne(name); + } + }); + } + + @Override + public void delete(Name name) { + Assert.notNull(name, "Id must not be null"); + ldapOperations.unbind(name); + } + + @Override + public void delete(T entity) { + Assert.notNull(entity, "Entity must not be null"); + ldapOperations.delete(entity); + } + + @Override + public void delete(Iterable entities) { + for (T entity : entities) { + delete(entity); + } + } + + @Override + public void deleteAll() { + delete(findAll()); + } + + private static class DelegatingIterable implements Iterable { + private final Iterable target; + private final Function function; + + private DelegatingIterable(Iterable target, Function function) { + this.target = target; + this.function = function; + } + + @Override + public Iterator iterator() { + final Iterator targetIterator = target.iterator(); + return new Iterator() { + @Override + public boolean hasNext() { + return targetIterator.hasNext(); + } + + @Override + public T next() { + return function.transform(targetIterator.next()); + } + + @Override + public void remove() { + throw new UnsupportedOperationException("Remove is not supported for this iterator"); + } + }; + } + } + + private interface Function { + T transform(F entry); + } +} diff --git a/core/src/main/java/org/springframework/ldap/repository/config/LdapRepositoryConfigurationExtension.java b/core/src/main/java/org/springframework/ldap/repository/config/LdapRepositoryConfigurationExtension.java new file mode 100644 index 00000000..727c5725 --- /dev/null +++ b/core/src/main/java/org/springframework/ldap/repository/config/LdapRepositoryConfigurationExtension.java @@ -0,0 +1,53 @@ +/* + * 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.repository.config; + +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport; +import org.springframework.data.repository.config.XmlRepositoryConfigurationSource; +import org.springframework.ldap.repository.LdapRepositoryFactoryBean; +import org.springframework.util.StringUtils; +import org.w3c.dom.Element; + +/** + * @author Mattias Hellborg Arthursson + */ +public class LdapRepositoryConfigurationExtension extends RepositoryConfigurationExtensionSupport { + + private static final String ATT_LDAP_TEMPLATE_REF = "ldap-template-ref"; + + @Override + protected String getModulePrefix() { + return "ldap"; + } + + @Override + public String getRepositoryFactoryClassName() { + return LdapRepositoryFactoryBean.class.getName(); + } + + @Override + public void postProcess(BeanDefinitionBuilder builder, XmlRepositoryConfigurationSource config) { + Element element = config.getElement(); + String ldapTemplateRef = element.getAttribute(ATT_LDAP_TEMPLATE_REF); + if(!StringUtils.hasText(ldapTemplateRef)) { + ldapTemplateRef = "ldapTemplate"; + } + + builder.addPropertyReference("ldapOperations", ldapTemplateRef); + } +} 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 1f4cf5be..d65d7809 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 @@ -1,9 +1,13 @@ - + + + @@ -445,4 +449,20 @@ + + + + + + + + + The reference to an LdapTemplate. Will default to 'ldapTemplate'. + + + + + + + \ No newline at end of file diff --git a/core/src/test/java/org/springframework/ldap/config/DummyEntity.java b/core/src/test/java/org/springframework/ldap/config/DummyEntity.java new file mode 100644 index 00000000..8d7eccef --- /dev/null +++ b/core/src/test/java/org/springframework/ldap/config/DummyEntity.java @@ -0,0 +1,7 @@ +package org.springframework.ldap.config; + +/** + * @author Mattias Hellborg Arthursson + */ +public class DummyEntity { +} diff --git a/core/src/test/java/org/springframework/ldap/config/DummyLdapRepository.java b/core/src/test/java/org/springframework/ldap/config/DummyLdapRepository.java new file mode 100644 index 00000000..77b24737 --- /dev/null +++ b/core/src/test/java/org/springframework/ldap/config/DummyLdapRepository.java @@ -0,0 +1,27 @@ +/* + * 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.data.repository.CrudRepository; + +import javax.naming.Name; + +/** + * @author Mattias Hellborg Arthursson + */ +public interface DummyLdapRepository extends CrudRepository { +} 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 efd09336..255de250 100644 --- a/core/src/test/java/org/springframework/ldap/config/LdapTemplateNamespaceHandlerTest.java +++ b/core/src/test/java/org/springframework/ldap/config/LdapTemplateNamespaceHandlerTest.java @@ -305,4 +305,12 @@ public class LdapTemplateNamespaceHandlerTest { public void verifyParseWithPoolingAndNativePoolingWillFail() { new ClassPathXmlApplicationContext("/ldap-namespace-config-pooling-with-native.xml"); } + + @Test + public void verifyAutomaticRepositorySupport() { + ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("/ldap-namespace-config-with-repositories.xml"); + DummyLdapRepository repository = ctx.getBean(DummyLdapRepository.class); + + assertNotNull(repository); + } } diff --git a/core/src/test/resources/ldap-namespace-config-with-repositories.xml b/core/src/test/resources/ldap-namespace-config-with-repositories.xml new file mode 100644 index 00000000..538e0bfd --- /dev/null +++ b/core/src/test/resources/ldap-namespace-config-with-repositories.xml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/gradle/java.gradle b/gradle/java.gradle index 891a71b5..04822b79 100644 --- a/gradle/java.gradle +++ b/gradle/java.gradle @@ -5,6 +5,7 @@ sourceCompatibility = '1.6' targetCompatibility = '1.6' ext.springVersion = '3.2.4.RELEASE' +ext.springDataVersion = '1.6.1.RELEASE' ext.springBatchVersion = '2.0.4.RELEASE' ext.junitVersion = '4.10' ext.commonsPoolVersion = '1.5.4' diff --git a/test/integration-tests/src/main/java/org/springframework/ldap/itest/odm/Person.java b/test/integration-tests/src/main/java/org/springframework/ldap/itest/odm/Person.java index 7f9bce24..c138de9d 100644 --- a/test/integration-tests/src/main/java/org/springframework/ldap/itest/odm/Person.java +++ b/test/integration-tests/src/main/java/org/springframework/ldap/itest/odm/Person.java @@ -1,8 +1,10 @@ package org.springframework.ldap.itest.odm; +import org.springframework.data.domain.Persistable; import org.springframework.ldap.odm.annotations.Attribute; import org.springframework.ldap.odm.annotations.Entry; import org.springframework.ldap.odm.annotations.Id; +import org.springframework.ldap.odm.annotations.Transient; import javax.naming.Name; import java.util.List; @@ -11,7 +13,7 @@ import java.util.List; * @author Mattias Hellborg Arthursson */ @Entry(objectClasses = { "inetOrgPerson", "organizationalPerson", "person", "top" }) -public class Person { +public class Person implements Persistable { @Id private Name dn; @@ -30,6 +32,19 @@ public class Person { @Attribute(name = "telephoneNumber") private String telephoneNumber; + @Transient + private boolean isNew; + + @Override + public Name getId() { + return getDn(); + } + + @Override + public boolean isNew() { + return isNew; + } + public Name getDn() { return dn; } diff --git a/test/integration-tests/src/main/java/org/springframework/ldap/itest/repositories/PersonRepository.java b/test/integration-tests/src/main/java/org/springframework/ldap/itest/repositories/PersonRepository.java new file mode 100644 index 00000000..ba2ce807 --- /dev/null +++ b/test/integration-tests/src/main/java/org/springframework/ldap/itest/repositories/PersonRepository.java @@ -0,0 +1,28 @@ +/* + * 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.itest.repositories; + +import org.springframework.data.repository.CrudRepository; +import org.springframework.ldap.itest.odm.Person; + +import javax.naming.Name; + +/** + * @author Mattias Hellborg Arthursson + */ +public interface PersonRepository extends CrudRepository { +} diff --git a/test/integration-tests/src/test/java/org/springframework/ldap/itest/repository/RepositoryScanITest.java b/test/integration-tests/src/test/java/org/springframework/ldap/itest/repository/RepositoryScanITest.java new file mode 100644 index 00000000..10c89b1d --- /dev/null +++ b/test/integration-tests/src/test/java/org/springframework/ldap/itest/repository/RepositoryScanITest.java @@ -0,0 +1,172 @@ +/* + * 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.itest.repository; + +import org.junit.Assert; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.ldap.core.LdapTemplate; +import org.springframework.ldap.itest.AbstractLdapTemplateIntegrationTest; +import org.springframework.ldap.itest.odm.Person; +import org.springframework.ldap.itest.repositories.PersonRepository; +import org.springframework.ldap.odm.core.OdmException; +import org.springframework.ldap.support.LdapNameBuilder; +import org.springframework.ldap.support.LdapUtils; +import org.springframework.test.context.ContextConfiguration; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; +import static org.springframework.ldap.query.LdapQueryBuilder.query; + +/** + * Tests for Spring LDAP automatic repository scan functionality. + * + * @author Ulrik Sandberg + */ +@ContextConfiguration(locations = {"/conf/repositoryScanTestContext.xml"}) +public class RepositoryScanITest extends AbstractLdapTemplateIntegrationTest { + @Autowired + private LdapTemplate ldapTemplate; + + @Autowired + private PersonRepository tested; + + + @Test + public void testFindOne() { + Person person = tested.findOne(LdapUtils.newLdapName("cn=Some Person3, ou=Company1, c=Sweden")); + + assertNotNull(person); + Assert.assertEquals("Some Person3", person.getCommonName()); + Assert.assertEquals("Person3", person.getSurname()); + Assert.assertEquals("Sweden, Company1, Some Person3", person.getDesc().get(0)); + Assert.assertEquals("+46 555-123654", person.getTelephoneNumber()); + } + +// @Test +// public void testFindByDn() { +// Person person = tested.findByDn(LdapUtils.newLdapName("cn=Some Person3,ou=company1,c=Sweden"), Person.class); +// +// assertNotNull(person); +// Assert.assertEquals("Some Person3", person.getCommonName()); +// Assert.assertEquals("Person3", person.getSurname()); +// Assert.assertEquals("Sweden, Company1, Some Person3", person.getDesc().get(0)); +// Assert.assertEquals("+46 555-123654", person.getTelephoneNumber()); +// } +// +// @Test(expected = OdmException.class) +// public void testFindByDnThrowsExceptionOnInvalidEntry() { +// tested.findByDn(LdapUtils.newLdapName("ou=company1,c=Sweden"), Person.class); +// } +// +// @Test(expected = EmptyResultDataAccessException.class) +// public void testFindOneThrowsEmptyResultIfNotFound() { +// tested.findOne(query() +// .where("cn").is("This cn does not exist"), Person.class); +// } +// +// @Test +// public void testFind() { +// List persons = tested.find(query() +// .where("cn").is("Some Person3"), Person.class); +// +// Assert.assertEquals(1, persons.size()); +// Person person = persons.get(0); +// +// assertNotNull(person); +// Assert.assertEquals("Some Person3", person.getCommonName()); +// Assert.assertEquals("Person3", person.getSurname()); +// Assert.assertEquals("Sweden, Company1, Some Person3", person.getDesc().get(0)); +// Assert.assertEquals("+46 555-123654", person.getTelephoneNumber()); +// } +// +// @Test +// public void testFindInCountry() { +// List persons = tested.find(query() +// .base("c=Sweden") +// .where("cn").isPresent(), Person.class); +// +// Assert.assertEquals(4, persons.size()); +// Person person = persons.get(0); +// +// assertNotNull(person); +// } +// +// @Test +// public void testFindAll() { +// List result = tested.findAll(Person.class); +// Assert.assertEquals(5, result.size()); +// } +// +// @Test +// public void testCreate() { +// Person person = new Person(); +// person.setDn(LdapNameBuilder.newLdapName("ou=company1,c=Sweden") +// .add("cn", "New Person").build()); +// person.setCommonName("New Person"); +// person.setSurname("Person"); +// person.setDesc(Arrays.asList("This is the description")); +// person.setTelephoneNumber("0123456"); +// +// tested.create(person); +// +// Assert.assertEquals(6, tested.findAll(Person.class).size()); +// +// person = tested.findOne(query() +// .where("cn").is("New Person"), Person.class); +// +// Assert.assertEquals("New Person", person.getCommonName()); +// Assert.assertEquals("Person", person.getSurname()); +// Assert.assertEquals("This is the description", person.getDesc().get(0)); +// Assert.assertEquals("0123456", person.getTelephoneNumber()); +// } +// +// @Test +// public void testUpdate() { +// Person person = tested.findOne(query() +// .where("cn").is("Some Person3"), Person.class); +// +// person.setDesc(Arrays.asList("New Description")); +// tested.update(person); +// +// person = tested.findOne(query() +// .where("cn").is("Some Person3"), Person.class); +// +// Assert.assertEquals("Some Person3", person.getCommonName()); +// Assert.assertEquals("Person3", person.getSurname()); +// Assert.assertEquals("New Description", person.getDesc().get(0)); +// Assert.assertEquals("+46 555-123654", person.getTelephoneNumber()); +// } +// +// @Test +// public void testDelete() { +// Person person = tested.findOne(query() +// .where("cn").is("Some Person3"), Person.class); +// +// tested.delete(person); +// +// try { +// tested.findOne(query().where("cn").is("Some Person3"), Person.class); +// fail("EmptyResultDataAccessException e"); +// } catch (EmptyResultDataAccessException e) { +// Assert.assertTrue(true); +// } +// } +} diff --git a/test/integration-tests/src/test/resources/conf/repositoryScanTestContext.xml b/test/integration-tests/src/test/resources/conf/repositoryScanTestContext.xml new file mode 100644 index 00000000..8c904aba --- /dev/null +++ b/test/integration-tests/src/test/resources/conf/repositoryScanTestContext.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + +