diff --git a/core/src/main/java/org/springframework/ldap/odm/annotations/Entry.java b/core/src/main/java/org/springframework/ldap/odm/annotations/Entry.java index 9fcf30da..b63832b6 100755 --- a/core/src/main/java/org/springframework/ldap/odm/annotations/Entry.java +++ b/core/src/main/java/org/springframework/ldap/odm/annotations/Entry.java @@ -1,5 +1,6 @@ package org.springframework.ldap.odm.annotations; +import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -13,6 +14,7 @@ import java.lang.annotation.Target; */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) +@Documented public @interface Entry { /** * A list of LDAP object classes that the annotated Java class represents. 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 26de34d6..eee53bee 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 @@ -78,6 +78,16 @@ public interface ObjectDirectoryMapper { */ Filter filterFor(Class clazz, Filter baseFilter); + /** + * Get the attribute corresponding to the specified field name. + * @param clazz the clazz. + * @param fieldName the field name. + * @return the attribute name. + * @throws IllegalArgumentException if the fieldName is not present in the class or if + * it is not mapped to an attribute. + */ + String attributeFor(Class clazz, String fieldName); + /** * Check if the specified class is already managed by this instance; if not, check the metadata and add the class to the * managed classes. 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 bb315fe8..acfb7bd3 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 @@ -425,6 +425,19 @@ public class DefaultObjectDirectoryMapper implements ObjectDirectoryMapper { return andFilter.append(ocFilter).append(baseFilter); } + @Override + public String attributeFor(Class clazz, String fieldName) { + try { + Field field = clazz.getDeclaredField(fieldName); + AttributeMetaData attributeMetaData = + getEntityData(clazz).metaData.getAttribute(field); + return attributeMetaData.getName().toString(); + } catch (NoSuchFieldException e) { + throw new IllegalArgumentException( + String.format("Field %s cannot be found in class %s", fieldName, clazz), e); + } + } + // For testing purposes ConcurrentMap, EntityData> getMetaDataMap() { return metaDataMap; 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 d78f4537..eeb754f7 100644 --- a/core/src/main/java/org/springframework/ldap/query/LdapQueryBuilder.java +++ b/core/src/main/java/org/springframework/ldap/query/LdapQueryBuilder.java @@ -199,11 +199,11 @@ public class LdapQueryBuilder implements LdapQuery { * @return this instance. * @throws IllegalStateException if a filter has already been specified. */ - public LdapQuery filter(String filterFormat, String... params) { + public LdapQuery filter(String filterFormat, Object... params) { Object[] encodedParams = new String[params.length]; for (int i=0; i < params.length; i++) { - encodedParams[i] = LdapEncoder.filterEncode(params[i]); + encodedParams[i] = LdapEncoder.filterEncode(params[i].toString()); } return filter(MessageFormat.format(filterFormat, encodedParams)); diff --git a/core/src/main/java/org/springframework/ldap/repository/Query.java b/core/src/main/java/org/springframework/ldap/repository/Query.java new file mode 100644 index 00000000..342556cf --- /dev/null +++ b/core/src/main/java/org/springframework/ldap/repository/Query.java @@ -0,0 +1,68 @@ +/* + * 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.ldap.query.SearchScope; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation for use in {@link org.springframework.ldap.repository.LdapRepository} declarations + * to create automatic query methods based on statically defined queries. + * + * @author Mattias Hellborg Arthursson + * @since 2.0 + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Query { + /** + * Search base, to be used as input to {@link org.springframework.ldap.query.LdapQueryBuilder#base(javax.naming.Name)}. + * + * @return the search base, default is {@link org.springframework.ldap.support.LdapUtils#emptyLdapName()} + */ + String base() default ""; + /** + * The filter format string, to be used as input to {@link org.springframework.ldap.query.LdapQueryBuilder#filter(String, Object...)}. + * + * @return search filter, must be specified. + */ + String value() default ""; + /** + * Search scope, to be used as input to {@link org.springframework.ldap.query.LdapQueryBuilder#searchScope(org.springframework.ldap.query.SearchScope)}. + * + * @return the search scope. + */ + SearchScope searchScope() default SearchScope.SUBTREE; + /** + * Time limit, to be used as input to {@link org.springframework.ldap.query.LdapQueryBuilder#timeLimit(int)}. + * + * @return the time limit. + */ + int timeLimit() default 0; + /** + * Count limit, to be used as input to {@link org.springframework.ldap.query.LdapQueryBuilder#countLimit(int)}. + * + * @return the count limit. + */ + int countLimit() default 0; +} diff --git a/core/src/main/java/org/springframework/ldap/repository/config/EnableLdapRepositories.java b/core/src/main/java/org/springframework/ldap/repository/config/EnableLdapRepositories.java new file mode 100644 index 00000000..51599cbc --- /dev/null +++ b/core/src/main/java/org/springframework/ldap/repository/config/EnableLdapRepositories.java @@ -0,0 +1,113 @@ +/* + * 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.context.annotation.ComponentScan.Filter; +import org.springframework.context.annotation.Import; +import org.springframework.data.repository.query.QueryLookupStrategy.Key; +import org.springframework.ldap.repository.support.LdapRepositoryFactoryBean; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to activate Ldap repositories. If no base package is configured through either {@link #value()}, + * {@link #basePackages()} or {@link #basePackageClasses()} it will trigger scanning of the package of annotated class. + * + * @author Mattias Hellborg Arthursson + * @since 2.0 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +@Import(LdapRepositoriesRegistrar.class) +public @interface EnableLdapRepositories { + + /** + * Alias for the {@link #basePackages()} attribute. Allows for more concise annotation declarations e.g.: + * {@code @EnableLdapRepositories("org.my.pkg")} instead of {@code @EnableLdapRepositories(basePackages="org.my.pkg")}. + */ + String[] value() default {}; + + /** + * Base packages to scan for annotated components. {@link #value()} is an alias for (and mutually exclusive with) this + * attribute. Use {@link #basePackageClasses()} for a type-safe alternative to String-based package names. + */ + String[] basePackages() default {}; + + /** + * Type-safe alternative to {@link #basePackages()} for specifying the packages to scan for annotated components. The + * package of each class specified will be scanned. Consider creating a special no-op marker class or interface in + * each package that serves no purpose other than being referenced by this attribute. + */ + Class[] basePackageClasses() default {}; + + /** + * Specifies which types are eligible for component scanning. Further narrows the set of candidate components from + * everything in {@link #basePackages()} to everything in the base packages that matches the given filter or filters. + */ + Filter[] includeFilters() default {}; + + /** + * Specifies which types are not eligible for component scanning. + */ + Filter[] excludeFilters() default {}; + + /** + * Returns the postfix to be used when looking up custom repository implementations. Defaults to {@literal Impl}. So + * for a repository named {@code PersonRepository} the corresponding implementation class will be looked up scanning + * for {@code PersonRepositoryImpl}. + * + * @return + */ + String repositoryImplementationPostfix() default ""; + + /** + * Configures the location of where to find the Spring Data named queries properties file. Will default to + * {@code META-INFO/mongo-named-queries.properties}. + * + * @return + */ + String namedQueriesLocation() default ""; + + /** + * Returns the key of the {@link org.springframework.data.repository.query.QueryLookupStrategy} to be used for lookup queries for query methods. Defaults to + * {@link org.springframework.data.repository.query.QueryLookupStrategy.Key#CREATE_IF_NOT_FOUND}. + * + * @return + */ + Key queryLookupStrategy() default Key.CREATE_IF_NOT_FOUND; + + /** + * Returns the {@link org.springframework.beans.factory.FactoryBean} class to be used for each repository instance. Defaults to + * {@link org.springframework.ldap.repository.support.LdapRepositoryFactoryBean}. + * + * @return + */ + Class repositoryFactoryBeanClass() default LdapRepositoryFactoryBean.class; + + /** + * Configures the name of the {@link org.springframework.ldap.core.LdapTemplate} bean to be used with the repositories detected. + * + * @return + */ + String ldapTemplateRef() default "ldapTemplate"; +} diff --git a/core/src/main/java/org/springframework/ldap/repository/config/LdapRepositoriesRegistrar.java b/core/src/main/java/org/springframework/ldap/repository/config/LdapRepositoriesRegistrar.java new file mode 100644 index 00000000..512be126 --- /dev/null +++ b/core/src/main/java/org/springframework/ldap/repository/config/LdapRepositoriesRegistrar.java @@ -0,0 +1,40 @@ +/* + * 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.data.repository.config.RepositoryBeanDefinitionRegistrarSupport; +import org.springframework.data.repository.config.RepositoryConfigurationExtension; + +import java.lang.annotation.Annotation; + +/** + * LDAP-specific {@link org.springframework.context.annotation.ImportBeanDefinitionRegistrar}. + * + * @author Mattias Hellborg Arthursson + * @since 2.0 + */ +class LdapRepositoriesRegistrar extends RepositoryBeanDefinitionRegistrarSupport { + + @Override + protected Class getAnnotation() { + return EnableLdapRepositories.class; + } + + @Override + protected RepositoryConfigurationExtension getExtension() { + return new LdapRepositoryConfigurationExtension(); + } +} 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 index 9a61aacb..b0395eb8 100644 --- a/core/src/main/java/org/springframework/ldap/repository/config/LdapRepositoryConfigurationExtension.java +++ b/core/src/main/java/org/springframework/ldap/repository/config/LdapRepositoryConfigurationExtension.java @@ -17,9 +17,11 @@ package org.springframework.ldap.repository.config; import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.data.repository.config.AnnotationRepositoryConfigurationSource; import org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport; import org.springframework.data.repository.config.XmlRepositoryConfigurationSource; -import org.springframework.ldap.repository.LdapRepositoryFactoryBean; +import org.springframework.ldap.repository.support.LdapRepositoryFactoryBean; import org.springframework.util.StringUtils; import org.w3c.dom.Element; @@ -51,4 +53,11 @@ public class LdapRepositoryConfigurationExtension extends RepositoryConfiguratio builder.addPropertyReference("ldapOperations", ldapTemplateRef); } + + @Override + public void postProcess(BeanDefinitionBuilder builder, AnnotationRepositoryConfigurationSource config) { + AnnotationAttributes attributes = config.getAttributes(); + + builder.addPropertyReference("ldapOperations", attributes.getString("ldapTemplateRef")); + } } diff --git a/core/src/main/java/org/springframework/ldap/repository/query/AbstractLdapRepositoryQuery.java b/core/src/main/java/org/springframework/ldap/repository/query/AbstractLdapRepositoryQuery.java new file mode 100644 index 00000000..ddbeeca3 --- /dev/null +++ b/core/src/main/java/org/springframework/ldap/repository/query/AbstractLdapRepositoryQuery.java @@ -0,0 +1,67 @@ +/* + * 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.query; + +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.data.repository.query.QueryMethod; +import org.springframework.data.repository.query.RepositoryQuery; +import org.springframework.ldap.core.LdapOperations; +import org.springframework.ldap.query.LdapQuery; + +/** + * @author Mattias Hellborg Arthursson + * @since 2.0 + */ +abstract class AbstractLdapRepositoryQuery implements RepositoryQuery { + private final LdapQueryMethod queryMethod; + private final Class clazz; + private final LdapOperations ldapOperations; + + public AbstractLdapRepositoryQuery(LdapQueryMethod queryMethod, + Class clazz, + LdapOperations ldapOperations) { + this.queryMethod = queryMethod; + this.clazz = clazz; + this.ldapOperations = ldapOperations; + } + + @Override + public final Object execute(Object[] parameters) { + LdapQuery query = createQuery(parameters); + + if(queryMethod.isCollectionQuery()) { + return ldapOperations.find(query, clazz); + } else { + try { + return ldapOperations.findOne(query, clazz); + } catch (EmptyResultDataAccessException e) { + return null; + } + } + } + + protected abstract LdapQuery createQuery(Object[] parameters); + + Class getClazz() { + return clazz; + } + + @Override + public final QueryMethod getQueryMethod() { + return queryMethod; + } +} diff --git a/core/src/main/java/org/springframework/ldap/repository/query/AnnotatedLdapRepositoryQuery.java b/core/src/main/java/org/springframework/ldap/repository/query/AnnotatedLdapRepositoryQuery.java new file mode 100644 index 00000000..ceb67e58 --- /dev/null +++ b/core/src/main/java/org/springframework/ldap/repository/query/AnnotatedLdapRepositoryQuery.java @@ -0,0 +1,58 @@ +/* + * 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.query; + +import org.springframework.ldap.core.LdapOperations; +import org.springframework.ldap.query.LdapQuery; +import org.springframework.ldap.repository.Query; +import org.springframework.util.Assert; + +import static org.springframework.ldap.query.LdapQueryBuilder.query; + +/** + * Handles queries for repository methods annotated with {@link org.springframework.ldap.repository.Query}. + * + * @author Mattias Hellborg Arthursson + * @since 2.0 + */ +public class AnnotatedLdapRepositoryQuery extends AbstractLdapRepositoryQuery { + private final Query queryAnnotation; + + /** + * Construct a new instance. + * @param queryMethod the QueryMethod. + * @param clazz the managed class. + * @param ldapOperations the LdapOperations instance to use. + */ + public AnnotatedLdapRepositoryQuery(LdapQueryMethod queryMethod, Class clazz, LdapOperations ldapOperations) { + super(queryMethod, clazz, ldapOperations); + queryAnnotation = queryMethod.getQueryAnnotation(); + + Assert.notNull(queryMethod, "Annotation must be present"); + Assert.hasLength(queryAnnotation.value(), "Query filter must be specified"); + } + + @Override + protected LdapQuery createQuery(Object[] parameters) { + return query() + .base(queryAnnotation.base()) + .searchScope(queryAnnotation.searchScope()) + .countLimit(queryAnnotation.countLimit()) + .timeLimit(queryAnnotation.timeLimit()) + .filter(queryAnnotation.value(), parameters); + } +} diff --git a/core/src/main/java/org/springframework/ldap/repository/query/LdapQueryCreator.java b/core/src/main/java/org/springframework/ldap/repository/query/LdapQueryCreator.java new file mode 100644 index 00000000..b2191c56 --- /dev/null +++ b/core/src/main/java/org/springframework/ldap/repository/query/LdapQueryCreator.java @@ -0,0 +1,86 @@ +/* + * 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.query; + +import org.springframework.data.domain.Sort; +import org.springframework.data.mapping.PropertyPath; +import org.springframework.data.repository.query.Parameters; +import org.springframework.data.repository.query.ParametersParameterAccessor; +import org.springframework.data.repository.query.parser.AbstractQueryCreator; +import org.springframework.data.repository.query.parser.Part; +import org.springframework.data.repository.query.parser.PartTree; +import org.springframework.ldap.odm.core.ObjectDirectoryMapper; +import org.springframework.ldap.query.ContainerCriteria; +import org.springframework.ldap.query.LdapQuery; + +import java.util.Iterator; + +import static org.springframework.ldap.query.LdapQueryBuilder.query; + +/** + * Creator of dynamic queries based on method names. + * @author Mattias Hellborg Arthursson + * @since 2.0 + */ +public class LdapQueryCreator extends AbstractQueryCreator { + private final Class clazz; + private final ObjectDirectoryMapper mapper; + + /** + * Construct a new instance. + */ + public LdapQueryCreator(PartTree tree, + Parameters parameters, + Class clazz, + ObjectDirectoryMapper mapper, + Object[] values) { + super(tree, new ParametersParameterAccessor(parameters, values)); + this.clazz = clazz; + this.mapper = mapper; + } + + @Override + protected ContainerCriteria create(Part part, Iterator iterator) { + return query() + .where(getAttribute(part)) + .is(iterator.next().toString()); + } + + private String getAttribute(Part part) { + PropertyPath path = part.getProperty(); + if(path.hasNext()) { + throw new IllegalArgumentException("Nested properties are not supported"); + } + + return mapper.attributeFor(clazz, path.getSegment()); + } + + @Override + protected ContainerCriteria and(Part part, ContainerCriteria base, Iterator iterator) { + return base.and(getAttribute(part)).is(iterator.next().toString()); + } + + @Override + protected ContainerCriteria or(ContainerCriteria base, ContainerCriteria criteria) { + return base.or(criteria); + } + + @Override + protected LdapQuery complete(ContainerCriteria criteria, Sort sort) { + return criteria; + } +} diff --git a/core/src/main/java/org/springframework/ldap/repository/query/LdapQueryMethod.java b/core/src/main/java/org/springframework/ldap/repository/query/LdapQueryMethod.java new file mode 100644 index 00000000..b444f3f3 --- /dev/null +++ b/core/src/main/java/org/springframework/ldap/repository/query/LdapQueryMethod.java @@ -0,0 +1,63 @@ +/* + * 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.query; + +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.data.repository.core.RepositoryMetadata; +import org.springframework.data.repository.query.QueryMethod; +import org.springframework.ldap.repository.Query; + +import java.lang.reflect.Method; + +/** + * QueryMethod for Ldap Queries. + * + * @author Mattias Hellborg Arthursson + * @since 2.0 + */ +public class LdapQueryMethod extends QueryMethod { + private final Method method; + + /** + * Creates a new LdapQueryMethod from the given parameters. + * + * @param method must not be {@literal null} + * @param metadata must not be {@literal null} + */ + public LdapQueryMethod(Method method, RepositoryMetadata metadata) { + super(method, metadata); + this.method = method; + } + + /** + * Check whether the target method is annotated with {@link org.springframework.ldap.repository.Query}. + * @return true if the target method is annotated with {@link org.springframework.ldap.repository.Query}, false + * otherwise. + */ + public boolean hasQueryAnnotation() { + return getQueryAnnotation() != null; + } + + /** + * Get the {@link org.springframework.ldap.repository.Query} annotation of the target method (if any). + * @return the {@link org.springframework.ldap.repository.Query} annotation of the target method if present, or null + * otherwise. + */ + Query getQueryAnnotation() { + return AnnotationUtils.getAnnotation(method, Query.class); + } +} diff --git a/core/src/main/java/org/springframework/ldap/repository/query/PartTreeLdapRepositoryQuery.java b/core/src/main/java/org/springframework/ldap/repository/query/PartTreeLdapRepositoryQuery.java new file mode 100644 index 00000000..af14f098 --- /dev/null +++ b/core/src/main/java/org/springframework/ldap/repository/query/PartTreeLdapRepositoryQuery.java @@ -0,0 +1,35 @@ +package org.springframework.ldap.repository.query; + +import org.springframework.data.repository.query.Parameters; +import org.springframework.data.repository.query.parser.PartTree; +import org.springframework.ldap.core.LdapOperations; +import org.springframework.ldap.odm.core.ObjectDirectoryMapper; +import org.springframework.ldap.query.LdapQuery; + +/** + * @author Mattias Hellborg Arthursson + */ +public class PartTreeLdapRepositoryQuery extends AbstractLdapRepositoryQuery { + private final PartTree partTree; + private final Parameters parameters; + private final ObjectDirectoryMapper objectDirectoryMapper; + + public PartTreeLdapRepositoryQuery(LdapQueryMethod queryMethod, Class clazz, LdapOperations ldapOperations) { + super(queryMethod, clazz, ldapOperations); + partTree = new PartTree(queryMethod.getName(), clazz); + parameters = queryMethod.getParameters(); + objectDirectoryMapper = ldapOperations.getObjectDirectoryMapper(); + } + + + @Override + protected LdapQuery createQuery(Object[] actualParameters) { + LdapQueryCreator queryCreator = + new LdapQueryCreator(partTree, + this.parameters, + getClazz(), + objectDirectoryMapper, + actualParameters); + return queryCreator.createQuery(); + } +} diff --git a/core/src/main/java/org/springframework/ldap/repository/LdapRepositoryFactory.java b/core/src/main/java/org/springframework/ldap/repository/support/LdapRepositoryFactory.java similarity index 56% rename from core/src/main/java/org/springframework/ldap/repository/LdapRepositoryFactory.java rename to core/src/main/java/org/springframework/ldap/repository/support/LdapRepositoryFactory.java index 57d73622..06241eab 100644 --- a/core/src/main/java/org/springframework/ldap/repository/LdapRepositoryFactory.java +++ b/core/src/main/java/org/springframework/ldap/repository/support/LdapRepositoryFactory.java @@ -14,17 +14,24 @@ * limitations under the License. */ -package org.springframework.ldap.repository; +package org.springframework.ldap.repository.support; import org.springframework.data.repository.core.EntityInformation; +import org.springframework.data.repository.core.NamedQueries; import org.springframework.data.repository.core.RepositoryMetadata; import org.springframework.data.repository.core.support.RepositoryFactorySupport; +import org.springframework.data.repository.query.QueryLookupStrategy; +import org.springframework.data.repository.query.RepositoryQuery; import org.springframework.ldap.core.LdapOperations; +import org.springframework.ldap.repository.query.AnnotatedLdapRepositoryQuery; +import org.springframework.ldap.repository.query.LdapQueryMethod; +import org.springframework.ldap.repository.query.PartTreeLdapRepositoryQuery; import java.io.Serializable; +import java.lang.reflect.Method; /** - * Factory to create {@link LdapRepository} instances. + * Factory to create {@link org.springframework.ldap.repository.LdapRepository} instances. * @author Mattias Hellborg Arthursson * @since 2.0 */ @@ -53,4 +60,23 @@ public class LdapRepositoryFactory extends RepositoryFactorySupport { protected Class getRepositoryBaseClass(RepositoryMetadata metadata) { return SimpleLdapRepository.class; } + + @Override + protected QueryLookupStrategy getQueryLookupStrategy(QueryLookupStrategy.Key key) { + return new LdapQueryLookupStrategy(); + } + + private final class LdapQueryLookupStrategy implements QueryLookupStrategy { + @Override + public RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata, NamedQueries namedQueries) { + LdapQueryMethod queryMethod = new LdapQueryMethod(method, metadata); + Class domainType = metadata.getDomainType(); + + if(queryMethod.hasQueryAnnotation()) { + return new AnnotatedLdapRepositoryQuery(queryMethod, domainType, ldapOperations); + } else { + return new PartTreeLdapRepositoryQuery(queryMethod, domainType, ldapOperations); + } + } + } } diff --git a/core/src/main/java/org/springframework/ldap/repository/LdapRepositoryFactoryBean.java b/core/src/main/java/org/springframework/ldap/repository/support/LdapRepositoryFactoryBean.java similarity index 93% rename from core/src/main/java/org/springframework/ldap/repository/LdapRepositoryFactoryBean.java rename to core/src/main/java/org/springframework/ldap/repository/support/LdapRepositoryFactoryBean.java index 08462225..66888a56 100644 --- a/core/src/main/java/org/springframework/ldap/repository/LdapRepositoryFactoryBean.java +++ b/core/src/main/java/org/springframework/ldap/repository/support/LdapRepositoryFactoryBean.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.ldap.repository; +package org.springframework.ldap.repository.support; import org.springframework.data.repository.Repository; import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport; @@ -25,7 +25,7 @@ import org.springframework.util.Assert; import javax.naming.Name; /** - * {@link org.springframework.beans.factory.FactoryBean} to create {@link LdapRepository} instances. + * {@link org.springframework.beans.factory.FactoryBean} to create {@link org.springframework.ldap.repository.LdapRepository} instances. * * @author Mattias Hellborg Arthursson * @since 2.0 diff --git a/core/src/main/java/org/springframework/ldap/repository/SimpleLdapRepository.java b/core/src/main/java/org/springframework/ldap/repository/support/SimpleLdapRepository.java similarity index 98% rename from core/src/main/java/org/springframework/ldap/repository/SimpleLdapRepository.java rename to core/src/main/java/org/springframework/ldap/repository/support/SimpleLdapRepository.java index 98cdb3d0..1c970f6c 100644 --- a/core/src/main/java/org/springframework/ldap/repository/SimpleLdapRepository.java +++ b/core/src/main/java/org/springframework/ldap/repository/support/SimpleLdapRepository.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.ldap.repository; +package org.springframework.ldap.repository.support; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.data.domain.Persistable; @@ -24,6 +24,7 @@ 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.ldap.repository.LdapRepository; import org.springframework.util.Assert; import javax.naming.Name; diff --git a/core/src/test/java/org/springframework/ldap/repository/SimpleLdapRepositoryTest.java b/core/src/test/java/org/springframework/ldap/repository/SimpleLdapRepositoryTest.java index 64f93559..9ceef01b 100644 --- a/core/src/test/java/org/springframework/ldap/repository/SimpleLdapRepositoryTest.java +++ b/core/src/test/java/org/springframework/ldap/repository/SimpleLdapRepositoryTest.java @@ -10,6 +10,7 @@ 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.ldap.repository.support.SimpleLdapRepository; import org.springframework.ldap.support.LdapUtils; import javax.naming.Name; diff --git a/core/src/test/java/org/springframework/ldap/repository/config/AnnotationConfigTest.java b/core/src/test/java/org/springframework/ldap/repository/config/AnnotationConfigTest.java new file mode 100644 index 00000000..e4fb65be --- /dev/null +++ b/core/src/test/java/org/springframework/ldap/repository/config/AnnotationConfigTest.java @@ -0,0 +1,21 @@ +package org.springframework.ldap.repository.config; + +import org.junit.Test; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.ldap.config.DummyLdapRepository; + +import static org.junit.Assert.assertNotNull; + +/** + * @author Mattias Hellborg Arthursson + */ +public class AnnotationConfigTest { + + @Test + public void testAnnotationConfig() { + ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("/ldap-annotation-config.xml"); + + DummyLdapRepository repository = ctx.getBean(DummyLdapRepository.class); + assertNotNull(repository); + } +} diff --git a/core/src/test/java/org/springframework/ldap/repository/config/SpringLdapConfiguration.java b/core/src/test/java/org/springframework/ldap/repository/config/SpringLdapConfiguration.java new file mode 100644 index 00000000..04164ee7 --- /dev/null +++ b/core/src/test/java/org/springframework/ldap/repository/config/SpringLdapConfiguration.java @@ -0,0 +1,11 @@ +package org.springframework.ldap.repository.config; + +import org.springframework.context.annotation.Configuration; + +/** + * @author Mattias Hellborg Arthursson + */ +@Configuration +@EnableLdapRepositories(basePackages = "org.springframework.ldap.config") +public class SpringLdapConfiguration { +} diff --git a/core/src/test/resources/ldap-annotation-config.xml b/core/src/test/resources/ldap-annotation-config.xml new file mode 100644 index 00000000..4d55bd6d --- /dev/null +++ b/core/src/test/resources/ldap-annotation-config.xml @@ -0,0 +1,12 @@ + + + + + + + + + \ No newline at end of file diff --git a/test/integration-tests/src/main/java/org/springframework/ldap/itest/config/SpringLdapConfiguration.java b/test/integration-tests/src/main/java/org/springframework/ldap/itest/config/SpringLdapConfiguration.java new file mode 100644 index 00000000..cde84231 --- /dev/null +++ b/test/integration-tests/src/main/java/org/springframework/ldap/itest/config/SpringLdapConfiguration.java @@ -0,0 +1,12 @@ +package org.springframework.ldap.itest.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.ldap.repository.config.EnableLdapRepositories; + +/** + * @author Mattias Hellborg Arthursson + */ +@Configuration +@EnableLdapRepositories(basePackages = "org.springframework.ldap.itest.repositories") +public class SpringLdapConfiguration { +} 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 index 79f8a972..a372ab8f 100644 --- 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 @@ -17,10 +17,18 @@ package org.springframework.ldap.itest.repositories; import org.springframework.ldap.itest.odm.Person; +import org.springframework.ldap.repository.Query; import org.springframework.ldap.repository.LdapRepository; /** * @author Mattias Hellborg Arthursson */ public interface PersonRepository extends LdapRepository { + @Query("(sn={0})") + Iterable findByLastName(String lastName); + + @Query("(uid={0})") + Person findByUid(String uid); + + Person findByTelephoneNumber(String phoneNumber); } diff --git a/test/integration-tests/src/test/java/org/springframework/ldap/itest/repository/RepositoryScanAnnotationConfiguredITest.java b/test/integration-tests/src/test/java/org/springframework/ldap/itest/repository/RepositoryScanAnnotationConfiguredITest.java new file mode 100644 index 00000000..01692774 --- /dev/null +++ b/test/integration-tests/src/test/java/org/springframework/ldap/itest/repository/RepositoryScanAnnotationConfiguredITest.java @@ -0,0 +1,61 @@ +/* + * 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.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.ldap.itest.AbstractLdapTemplateIntegrationTest; +import org.springframework.ldap.itest.odm.Person; +import org.springframework.ldap.itest.repositories.PersonRepository; +import org.springframework.ldap.support.LdapUtils; +import org.springframework.test.context.ContextConfiguration; + +import javax.naming.Name; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +/** + * Tests for Spring LDAP automatic repository scan functionality enabled with + * {@link org.springframework.ldap.repository.config.EnableLdapRepositories}. + * + * @author Mattias Hellborg Arthursson + */ +@ContextConfiguration(locations = {"/conf/repositoryScanAnnotationTestContext.xml"}) +public class RepositoryScanAnnotationConfiguredITest extends AbstractLdapTemplateIntegrationTest { + private static final Name PERSON3_DN = LdapUtils.newLdapName("cn=Some Person3, ou=Company1, c=Sweden"); + + @Autowired + private PersonRepository tested; + + @Test + public void testExists() { + assertTrue(tested.exists(PERSON3_DN)); + } + + @Test + public void testFindOneWithDn() { + Person person = tested.findOne(PERSON3_DN); + + assertNotNull(person); + assertEquals("Some Person3", person.getCommonName()); + assertEquals("Person3", person.getSurname()); + assertEquals("Sweden, Company1, Some Person3", person.getDesc().get(0)); + assertEquals("+46 555-123654", person.getTelephoneNumber()); + } +} 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 index 6058ca6b..7c4a188f 100644 --- 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 @@ -19,7 +19,6 @@ package org.springframework.ldap.itest.repository; import org.apache.commons.lang.StringUtils; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; -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; @@ -236,4 +235,31 @@ public class RepositoryScanITest extends AbstractLdapTemplateIntegrationTest { assertEquals(3, tested.count()); } + + @Test + public void testFindByLastName() { + Iterable found = tested.findByLastName("Person3"); + assertEquals(1, countIterable(found)); + } + + @Test + public void testFindByUid() { + Person person = tested.findByUid("some.person3"); + + assertEquals("Some Person3", person.getCommonName()); + assertEquals("Person3", person.getSurname()); + assertEquals("Sweden, Company1, Some Person3", person.getDesc().get(0)); + assertEquals("+46 555-123654", person.getTelephoneNumber()); + } + + @Test + public void testFindByPhoneNumber() { + Person person = tested.findByTelephoneNumber("+46 555-123654"); + + assertEquals("Some Person3", person.getCommonName()); + assertEquals("Person3", person.getSurname()); + assertEquals("Sweden, Company1, Some Person3", person.getDesc().get(0)); + assertEquals("+46 555-123654", person.getTelephoneNumber()); + } + } diff --git a/test/integration-tests/src/test/resources/conf/repositoryScanAnnotationTestContext.xml b/test/integration-tests/src/test/resources/conf/repositoryScanAnnotationTestContext.xml new file mode 100644 index 00000000..79f0dfc0 --- /dev/null +++ b/test/integration-tests/src/test/resources/conf/repositoryScanAnnotationTestContext.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + +