From 7823b329b425aa14153f998c2c5a7ed2bd3c2f6e Mon Sep 17 00:00:00 2001 From: Mattias Hellborg Arthursson Date: Tue, 22 Oct 2013 08:06:12 +0200 Subject: [PATCH] LDAP-269: Basic QueryDSL support. --- core/build.gradle | 3 +- ...tLdapAnnotationProcessorConfiguration.java | 56 ++++++++ .../support/LdapAnnotationProcessor.java | 53 +++++++ .../support/LdapRepositoryFactory.java | 30 +++- .../repository/support/LdapSerializer.java | 135 ++++++++++++++++++ .../repository/support/QueryDslLdapQuery.java | 75 ++++++++++ .../support/QueryDslLdapRepository.java | 72 ++++++++++ .../support/SimpleLdapRepository.java | 17 ++- .../ldap/repository/support/QPerson.java | 45 ++++++ .../support/QueryDslFilterGeneratorTest.java | 106 ++++++++++++++ gradle/java.gradle | 1 + src/docbkx/overview.xml | 8 +- src/docbkx/repositories.xml | 84 +++++++---- test/integration-tests/build.gradle | 56 +++++++- .../PersonQueryDslRepository.java | 11 ++ .../LdapTemplateQueryDslLdapQueryITest.java | 69 +++++++++ .../RepositoryScanQueryDslITest.java | 78 ++++++++++ 17 files changed, 856 insertions(+), 43 deletions(-) create mode 100644 core/src/main/java/org/springframework/ldap/repository/support/DefaultLdapAnnotationProcessorConfiguration.java create mode 100644 core/src/main/java/org/springframework/ldap/repository/support/LdapAnnotationProcessor.java create mode 100644 core/src/main/java/org/springframework/ldap/repository/support/LdapSerializer.java create mode 100644 core/src/main/java/org/springframework/ldap/repository/support/QueryDslLdapQuery.java create mode 100644 core/src/main/java/org/springframework/ldap/repository/support/QueryDslLdapRepository.java create mode 100644 core/src/test/java/org/springframework/ldap/repository/support/QPerson.java create mode 100644 core/src/test/java/org/springframework/ldap/repository/support/QueryDslFilterGeneratorTest.java create mode 100644 test/integration-tests/src/main/java/org/springframework/ldap/itest/repositories/PersonQueryDslRepository.java create mode 100644 test/integration-tests/src/test/java/org/springframework/ldap/itest/odm/LdapTemplateQueryDslLdapQueryITest.java create mode 100644 test/integration-tests/src/test/java/org/springframework/ldap/itest/repository/RepositoryScanQueryDslITest.java diff --git a/core/build.gradle b/core/build.gradle index c84e40b0..7eb89458 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -18,7 +18,8 @@ dependencies { "com.sun:ldapbp:1.0", "org.springframework:spring-context:$springVersion", "org.springframework:spring-jdbc:$springVersion", - "org.springframework:spring-orm:$springVersion" + "org.springframework:spring-orm:$springVersion", + "com.mysema.querydsl:querydsl-apt:$queryDslVersion" testCompile "junit:junit:$junitVersion", "commons-lang:commons-lang:$commonsLangVersion", diff --git a/core/src/main/java/org/springframework/ldap/repository/support/DefaultLdapAnnotationProcessorConfiguration.java b/core/src/main/java/org/springframework/ldap/repository/support/DefaultLdapAnnotationProcessorConfiguration.java new file mode 100644 index 00000000..e0df7926 --- /dev/null +++ b/core/src/main/java/org/springframework/ldap/repository/support/DefaultLdapAnnotationProcessorConfiguration.java @@ -0,0 +1,56 @@ +/* + * 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.support; + +import com.mysema.query.apt.DefaultConfiguration; +import org.springframework.ldap.odm.annotations.Id; + +import javax.annotation.processing.RoundEnvironment; +import javax.lang.model.element.VariableElement; +import java.lang.annotation.Annotation; +import java.util.Collection; +import java.util.Map; + +/** + * @author Mattias Hellborg Arthursson + * @since 2.0 + */ +class DefaultLdapAnnotationProcessorConfiguration extends DefaultConfiguration { + public DefaultLdapAnnotationProcessorConfiguration( + RoundEnvironment roundEnv, + Map options, + Collection keywords, + Class entitiesAnn, + Class entityAnn, + Class superTypeAnn, + Class embeddableAnn, + Class embeddedAnn, + Class skipAnn) { + + super(roundEnv, options, keywords, entitiesAnn, entityAnn, superTypeAnn, embeddableAnn, embeddedAnn, skipAnn); + } + + @Override + public boolean isBlockedField(VariableElement field) { + return super.isBlockedField(field) || field.getAnnotation(Id.class) != null; + } + + @Override + public boolean isValidField(VariableElement field) { + return super.isValidField(field) && field.getAnnotation(Id.class) == null; + } +} diff --git a/core/src/main/java/org/springframework/ldap/repository/support/LdapAnnotationProcessor.java b/core/src/main/java/org/springframework/ldap/repository/support/LdapAnnotationProcessor.java new file mode 100644 index 00000000..49925ad3 --- /dev/null +++ b/core/src/main/java/org/springframework/ldap/repository/support/LdapAnnotationProcessor.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.support; + +import com.mysema.query.annotations.QueryEntities; +import com.mysema.query.apt.AbstractQuerydslProcessor; +import com.mysema.query.apt.Configuration; +import com.mysema.query.apt.DefaultConfiguration; +import org.springframework.ldap.odm.annotations.Entry; +import org.springframework.ldap.odm.annotations.Transient; + +import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.annotation.processing.SupportedSourceVersion; +import javax.lang.model.SourceVersion; +import javax.tools.Diagnostic; +import java.util.Collections; + +/** + * QueryDSL Annotation Processor to generate QueryDSL classes for entity classes annotated with {@link Entry}. + * + * @author Mattias Hellborg Arthursson + * @since 2.0 + */ +@SupportedAnnotationTypes("org.springframework.ldap.odm.annotations.*") +@SupportedSourceVersion(SourceVersion.RELEASE_6) +public class LdapAnnotationProcessor extends AbstractQuerydslProcessor { + @Override + protected Configuration createConfiguration(RoundEnvironment roundEnv) { + processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "Running " + getClass().getSimpleName()); + + DefaultConfiguration configuration = new DefaultLdapAnnotationProcessorConfiguration(roundEnv, processingEnv.getOptions(), + Collections.emptySet(), QueryEntities.class, Entry.class, null, null, null, Transient.class); + configuration.setUseFields(true); + configuration.setUseGetters(false); + + return configuration; + } +} diff --git a/core/src/main/java/org/springframework/ldap/repository/support/LdapRepositoryFactory.java b/core/src/main/java/org/springframework/ldap/repository/support/LdapRepositoryFactory.java index 06241eab..41338213 100644 --- a/core/src/main/java/org/springframework/ldap/repository/support/LdapRepositoryFactory.java +++ b/core/src/main/java/org/springframework/ldap/repository/support/LdapRepositoryFactory.java @@ -16,6 +16,7 @@ package org.springframework.ldap.repository.support; +import org.springframework.data.querydsl.QueryDslPredicateExecutor; import org.springframework.data.repository.core.EntityInformation; import org.springframework.data.repository.core.NamedQueries; import org.springframework.data.repository.core.RepositoryMetadata; @@ -30,6 +31,8 @@ import org.springframework.ldap.repository.query.PartTreeLdapRepositoryQuery; import java.io.Serializable; import java.lang.reflect.Method; +import static org.springframework.data.querydsl.QueryDslUtils.QUERY_DSL_PRESENT; + /** * Factory to create {@link org.springframework.ldap.repository.LdapRepository} instances. * @author Mattias Hellborg Arthursson @@ -50,15 +53,32 @@ public class LdapRepositoryFactory extends RepositoryFactorySupport { @Override @SuppressWarnings({"unchecked", "rawtypes"}) protected Object getTargetRepository(RepositoryMetadata metadata) { - return new SimpleLdapRepository( - ldapOperations, - ldapOperations.getObjectDirectoryMapper(), - metadata.getDomainType()); + if(!isQueryDslRepository(metadata.getRepositoryInterface())) { + return new SimpleLdapRepository( + ldapOperations, + ldapOperations.getObjectDirectoryMapper(), + metadata.getDomainType()); + } else { + return new QueryDslLdapRepository( + ldapOperations, + ldapOperations.getObjectDirectoryMapper(), + metadata.getDomainType()); + } + } + private static boolean isQueryDslRepository(Class repositoryInterface) { + return QUERY_DSL_PRESENT && QueryDslPredicateExecutor.class.isAssignableFrom(repositoryInterface); + } + + @Override protected Class getRepositoryBaseClass(RepositoryMetadata metadata) { - return SimpleLdapRepository.class; + if(!isQueryDslRepository(metadata.getRepositoryInterface())) { + return SimpleLdapRepository.class; + } else { + return QueryDslLdapRepository.class; + } } @Override diff --git a/core/src/main/java/org/springframework/ldap/repository/support/LdapSerializer.java b/core/src/main/java/org/springframework/ldap/repository/support/LdapSerializer.java new file mode 100644 index 00000000..9e2e3fdd --- /dev/null +++ b/core/src/main/java/org/springframework/ldap/repository/support/LdapSerializer.java @@ -0,0 +1,135 @@ +/* + * 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.support; + +import com.mysema.query.types.Constant; +import com.mysema.query.types.Expression; +import com.mysema.query.types.FactoryExpression; +import com.mysema.query.types.Operation; +import com.mysema.query.types.Operator; +import com.mysema.query.types.Ops; +import com.mysema.query.types.ParamExpression; +import com.mysema.query.types.Path; +import com.mysema.query.types.SubQueryExpression; +import com.mysema.query.types.TemplateExpression; +import com.mysema.query.types.Visitor; +import org.springframework.ldap.filter.AndFilter; +import org.springframework.ldap.filter.EqualsFilter; +import org.springframework.ldap.filter.Filter; +import org.springframework.ldap.filter.GreaterThanOrEqualsFilter; +import org.springframework.ldap.filter.LessThanOrEqualsFilter; +import org.springframework.ldap.filter.LikeFilter; +import org.springframework.ldap.filter.NotFilter; +import org.springframework.ldap.filter.OrFilter; +import org.springframework.ldap.filter.PresentFilter; +import org.springframework.ldap.odm.core.ObjectDirectoryMapper; + +/** + * Helper class for generating LDAP filters from QueryDSL Expressions. + * + * @author Mattias Hellborg Arthursson + * @since 2.0 + */ +class LdapSerializer implements Visitor { + + private final ObjectDirectoryMapper odm; + private final Class clazz; + + public LdapSerializer(ObjectDirectoryMapper odm, Class clazz) { + this.odm = odm; + this.clazz = clazz; + } + + public Filter handle(Expression expression) { + return (Filter) expression.accept(this, null); + } + + @Override + public Object visit(Constant expr, Void context) { + return expr.getConstant().toString(); + } + + @Override + public Object visit(FactoryExpression expr, Void context) { + throw new UnsupportedOperationException(); + } + + @Override + public Object visit(Operation expr, Void context) { + Operator operator = expr.getOperator(); + if (operator == Ops.EQ) { + return new EqualsFilter(attribute(expr), value(expr)); + } else if (operator == Ops.AND) { + return new AndFilter() + .and(handle(expr.getArg(0))) + .and(handle(expr.getArg(1))); + } else if (operator == Ops.OR) { + return new OrFilter() + .or(handle(expr.getArg(0))) + .or(handle(expr.getArg(1))); + } else if (operator == Ops.NOT) { + return new NotFilter(handle(expr.getArg(0))); + } else if (operator == Ops.LIKE) { + return new LikeFilter(attribute(expr), value(expr)); + } else if (operator == Ops.STARTS_WITH || operator == Ops.STARTS_WITH_IC) { + return new LikeFilter(attribute(expr), value(expr) + "*"); + } else if (operator == Ops.ENDS_WITH || operator == Ops.ENDS_WITH_IC) { + return new LikeFilter(attribute(expr), "*" + value(expr)); + } else if (operator == Ops.STRING_CONTAINS || operator == Ops.STRING_CONTAINS_IC) { + return new LikeFilter(attribute(expr), "*" + value(expr) + "*"); + } else if (operator == Ops.IS_NOT_NULL) { + return new PresentFilter(attribute(expr)); + } else if (operator == Ops.IS_NULL) { + return new NotFilter(new PresentFilter(attribute(expr))); + } else if (operator == Ops.GOE) { + return new GreaterThanOrEqualsFilter(attribute(expr), value(expr)); + } else if (operator == Ops.LOE) { + return new LessThanOrEqualsFilter(attribute(expr), value(expr)); + } + + + throw new UnsupportedOperationException("Unsupported operator " + operator.toString()); + } + + private String value(Operation expr) { + return (String) expr.getArg(1).accept(this, null); + } + + private String attribute(Operation expr) { + return odm.attributeFor(clazz, (String) expr.getArg(0).accept(this, null)); + } + + @Override + public Object visit(ParamExpression expr, Void context) { + throw new UnsupportedOperationException(); + } + + @Override + public Object visit(Path expr, Void context) { + return expr.getMetadata().getName(); + } + + @Override + public Object visit(SubQueryExpression expr, Void context) { + throw new UnsupportedOperationException(); + } + + @Override + public Object visit(TemplateExpression expr, Void context) { + throw new UnsupportedOperationException(); + } +} diff --git a/core/src/main/java/org/springframework/ldap/repository/support/QueryDslLdapQuery.java b/core/src/main/java/org/springframework/ldap/repository/support/QueryDslLdapQuery.java new file mode 100644 index 00000000..6926b942 --- /dev/null +++ b/core/src/main/java/org/springframework/ldap/repository/support/QueryDslLdapQuery.java @@ -0,0 +1,75 @@ +/* + * 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.support; + +import com.mysema.query.DefaultQueryMetadata; +import com.mysema.query.FilteredClause; +import com.mysema.query.support.QueryMixin; +import com.mysema.query.types.EntityPath; +import com.mysema.query.types.Predicate; +import org.springframework.ldap.core.LdapOperations; +import org.springframework.ldap.query.LdapQuery; + +import java.util.List; + +import static org.springframework.ldap.query.LdapQueryBuilder.query; + +/** + * Spring LDAP specific {@link FilteredClause} implementation. + * + * @author Mattias Hellborg Arthursson + * @since 2.0 + */ +public class QueryDslLdapQuery implements FilteredClause> { + private final LdapOperations ldapOperations; + private final Class clazz; + + private QueryMixin> queryMixin = + new QueryMixin>(this, new DefaultQueryMetadata().noValidate()); + + private final LdapSerializer filterGenerator; + + @SuppressWarnings("unchecked") + public QueryDslLdapQuery(LdapOperations ldapOperations, EntityPath entityPath) { + this(ldapOperations, (Class) entityPath.getType()); + } + + public QueryDslLdapQuery(LdapOperations ldapOperations, Class clazz) { + this.ldapOperations = ldapOperations; + this.clazz = clazz; + this.filterGenerator = new LdapSerializer(ldapOperations.getObjectDirectoryMapper(), clazz); + } + + @Override + public QueryDslLdapQuery where(Predicate... o) { + return queryMixin.where(o); + } + + @SuppressWarnings("unchecked") + public List list() { + return (List) ldapOperations.find(buildQuery(), clazz); + } + + public K uniqueResult() { + return ldapOperations.findOne(buildQuery(), clazz); + } + + LdapQuery buildQuery() { + return query().filter(filterGenerator.handle(queryMixin.getMetadata().getWhere())); + } + +} diff --git a/core/src/main/java/org/springframework/ldap/repository/support/QueryDslLdapRepository.java b/core/src/main/java/org/springframework/ldap/repository/support/QueryDslLdapRepository.java new file mode 100644 index 00000000..a9dadb43 --- /dev/null +++ b/core/src/main/java/org/springframework/ldap/repository/support/QueryDslLdapRepository.java @@ -0,0 +1,72 @@ +/* + * 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.support; + +import com.mysema.query.types.OrderSpecifier; +import com.mysema.query.types.Predicate; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.querydsl.QueryDslPredicateExecutor; +import org.springframework.ldap.core.LdapOperations; +import org.springframework.ldap.odm.core.ObjectDirectoryMapper; + +import java.util.List; + +/** + * Base repository implementation for QueryDSL support. + * + * @author Mattias Hellborg Arthursson + * @since 2.0 + */ +public class QueryDslLdapRepository extends SimpleLdapRepository implements QueryDslPredicateExecutor { + + public QueryDslLdapRepository(LdapOperations ldapOperations, + ObjectDirectoryMapper odm, + Class clazz) { + super(ldapOperations, odm, clazz); + } + + @Override + public T findOne(Predicate predicate) { + return queryFor(predicate).uniqueResult(); + } + + @Override + public List findAll(Predicate predicate) { + return queryFor(predicate).list(); + } + + @Override + public long count(Predicate predicate) { + return findAll(predicate).size(); + } + + private QueryDslLdapQuery queryFor(Predicate predicate) { + return new QueryDslLdapQuery(getLdapOperations(), getClazz()) + .where(predicate); + } + + @Override + public Iterable findAll(Predicate predicate, OrderSpecifier... orders) { + throw new UnsupportedOperationException(); + } + + @Override + public Page findAll(Predicate predicate, Pageable pageable) { + throw new UnsupportedOperationException(); + } +} diff --git a/core/src/main/java/org/springframework/ldap/repository/support/SimpleLdapRepository.java b/core/src/main/java/org/springframework/ldap/repository/support/SimpleLdapRepository.java index 1c970f6c..cbbf3e0e 100644 --- a/core/src/main/java/org/springframework/ldap/repository/support/SimpleLdapRepository.java +++ b/core/src/main/java/org/springframework/ldap/repository/support/SimpleLdapRepository.java @@ -30,6 +30,7 @@ import org.springframework.util.Assert; import javax.naming.Name; import java.util.Iterator; import java.util.LinkedList; +import java.util.List; import static org.springframework.ldap.query.LdapQueryBuilder.query; @@ -51,6 +52,14 @@ public class SimpleLdapRepository implements LdapRepository { this.clazz = clazz; } + protected LdapOperations getLdapOperations() { + return ldapOperations; + } + + protected Class getClazz() { + return clazz; + } + @Override public long count() { Filter filter = odm.filterFor(clazz, null); @@ -63,7 +72,7 @@ public class SimpleLdapRepository implements LdapRepository { private boolean isNew(S entity, Name id) { if (entity instanceof Persistable) { - Persistable persistable = (Persistable) entity; + Persistable persistable = (Persistable) entity; return persistable.isNew(); } else { return id == null; @@ -119,7 +128,7 @@ public class SimpleLdapRepository implements LdapRepository { } @Override - public Iterable findAll(LdapQuery ldapQuery) { + public List findAll(LdapQuery ldapQuery) { Assert.notNull(ldapQuery, "LdapQuery must not be null"); return ldapOperations.find(ldapQuery, clazz); } @@ -141,12 +150,12 @@ public class SimpleLdapRepository implements LdapRepository { } @Override - public Iterable findAll() { + public List findAll() { return ldapOperations.findAll(clazz); } @Override - public Iterable findAll(final Iterable names) { + public List findAll(final Iterable names) { Iterable found = new TransformingIterable(names, new Function() { @Override public T transform(Name name) { diff --git a/core/src/test/java/org/springframework/ldap/repository/support/QPerson.java b/core/src/test/java/org/springframework/ldap/repository/support/QPerson.java new file mode 100644 index 00000000..a33de2c8 --- /dev/null +++ b/core/src/test/java/org/springframework/ldap/repository/support/QPerson.java @@ -0,0 +1,45 @@ +package org.springframework.ldap.repository.support; + +import com.mysema.query.types.Path; +import com.mysema.query.types.PathMetadata; +import com.mysema.query.types.path.EntityPathBase; +import com.mysema.query.types.path.ListPath; +import com.mysema.query.types.path.PathInits; +import com.mysema.query.types.path.StringPath; +import org.springframework.ldap.odm.core.impl.UnitTestPerson; + +import javax.annotation.Generated; + +import static com.mysema.query.types.PathMetadataFactory.forVariable; + + +/** + * QPerson is a Querydsl query type for Person + */ +@Generated("com.mysema.query.codegen.EntitySerializer") +public class QPerson extends EntityPathBase { + + private static final long serialVersionUID = -1526737794; + + public static final QPerson person = new QPerson("person"); + + public final StringPath fullName = createString("fullName"); + + public final ListPath description = this.createList("description", String.class, StringPath.class, PathInits.DIRECT2); + + public final StringPath lastName = createString("lastName"); + + public QPerson(String variable) { + super(UnitTestPerson.class, forVariable(variable)); + } + + public QPerson(Path path) { + super(path.getType(), path.getMetadata()); + } + + public QPerson(PathMetadata metadata) { + super(UnitTestPerson.class, metadata); + } + +} + diff --git a/core/src/test/java/org/springframework/ldap/repository/support/QueryDslFilterGeneratorTest.java b/core/src/test/java/org/springframework/ldap/repository/support/QueryDslFilterGeneratorTest.java new file mode 100644 index 00000000..6c26fbd6 --- /dev/null +++ b/core/src/test/java/org/springframework/ldap/repository/support/QueryDslFilterGeneratorTest.java @@ -0,0 +1,106 @@ +package org.springframework.ldap.repository.support; + +import com.mysema.query.types.Expression; +import org.junit.Before; +import org.junit.Test; +import org.springframework.ldap.filter.Filter; +import org.springframework.ldap.odm.core.ObjectDirectoryMapper; +import org.springframework.ldap.odm.core.impl.DefaultObjectDirectoryMapper; +import org.springframework.ldap.odm.core.impl.UnitTestPerson; + +import static org.junit.Assert.assertEquals; + +/** + * @author Mattias Hellborg Arthursson + */ +public class QueryDslFilterGeneratorTest { + + private LdapSerializer tested; + private QPerson person; + + @Before + public void prepareTestedInstance() { + ObjectDirectoryMapper odm = new DefaultObjectDirectoryMapper(); + tested = new LdapSerializer(odm, UnitTestPerson.class); + person = QPerson.person; + } + + @Test + public void testEqualsFilter() { + Expression expression = person.fullName.eq("John Doe"); + Filter result = tested.handle(expression); + assertEquals("(cn=John Doe)", result.toString()); + } + + @Test + public void testAndFilter() { + Expression expression = person.fullName.eq("John Doe").and(person.lastName.eq("Doe")); + Filter result = tested.handle(expression); + assertEquals("(&(cn=John Doe)(sn=Doe))", result.toString()); + } + + @Test + public void testOrFilter() { + Expression expression = person.fullName.eq("John Doe").or(person.lastName.eq("Doe")); + Filter result = tested.handle(expression); + assertEquals("(|(cn=John Doe)(sn=Doe))", result.toString()); + } + + @Test + public void testOr() { + Expression expression = person.fullName.eq("John Doe") + .and(person.lastName.eq("Doe").or(person.lastName.eq("Die"))); + + Filter result = tested.handle(expression); + assertEquals("(&(cn=John Doe)(|(sn=Doe)(sn=Die)))", result.toString()); + } + + @Test + public void testNot() { + Expression expression = person.fullName.eq("John Doe").not(); + Filter result = tested.handle(expression); + assertEquals("(!(cn=John Doe))", result.toString()); + } + + @Test + public void testIsLike() { + Expression expression = person.fullName.like("kalle*"); + Filter result = tested.handle(expression); + assertEquals("(cn=kalle*)", result.toString()); + } + + @Test + public void testStartsWith() { + Expression expression = person.fullName.startsWith("kalle"); + Filter result = tested.handle(expression); + assertEquals("(cn=kalle*)", result.toString()); + } + + @Test + public void testEndsWith() { + Expression expression = person.fullName.endsWith("kalle"); + Filter result = tested.handle(expression); + assertEquals("(cn=*kalle)", result.toString()); + } + + @Test + public void testContains() { + Expression expression = person.fullName.contains("kalle"); + Filter result = tested.handle(expression); + assertEquals("(cn=*kalle*)", result.toString()); + } + + @Test + public void testNotNull() { + Expression expression = person.fullName.isNotNull(); + Filter result = tested.handle(expression); + assertEquals("(cn=*)", result.toString()); + } + + @Test + public void testNull() { + Expression expression = person.fullName.isNull(); + Filter result = tested.handle(expression); + assertEquals("(!(cn=*))", result.toString()); + } +} diff --git a/gradle/java.gradle b/gradle/java.gradle index 04822b79..75085ca5 100644 --- a/gradle/java.gradle +++ b/gradle/java.gradle @@ -14,6 +14,7 @@ ext.commonsLoggingVersion = '1.1.1' ext.gsbaseVersion = '2.0.1' ext.log4jVersion = '1.2.15' ext.mockitoVersion = '1.9.5' +ext.queryDslVersion = '3.2.4' repositories { mavenCentral() diff --git a/src/docbkx/overview.xml b/src/docbkx/overview.xml index 740d61c1..805204fe 100644 --- a/src/docbkx/overview.xml +++ b/src/docbkx/overview.xml @@ -245,7 +245,9 @@ public class PersonDaoImpl implements PersonDao { Below is a list of the most important changes in Spring LDAP 2.0. - Java 1.6 is now required when using Spring LDAP. Spring versions starting at 2.0 and up are still supported. + + Java 1.6 is now required when using Spring LDAP. Spring versions starting at 2.0 and up are still supported. + The central API has been updated with Java 5 features such as generics and varargs. As a consequence, the entire spring-ldap-tiger module has been deprecated and users are encouraged to migrate @@ -262,6 +264,10 @@ public class PersonDaoImpl implements PersonDao { A custom XML namespace is now provided to simplify configuration of Spring LDAP. See for more information. + + Spring Data Repository and QueryDSL support is now included in Spring LDAP. + See for more information. + DistinguishedName and associated classes have been deprecated in favor of standard Java LdapName. See for information on how the library diff --git a/src/docbkx/repositories.xml b/src/docbkx/repositories.xml index 46328f4c..33aa5b4d 100644 --- a/src/docbkx/repositories.xml +++ b/src/docbkx/repositories.xml @@ -1,34 +1,58 @@ - Spring LDAP Repositories + Spring LDAP Repositories - - Overview - - - Spring LDAP has built-in support for Spring Data repositories. The basic functionality and configuration is described here. - When working with Spring LDAP repositories, please note the following: - - - Spring LDAP repositories can be enabled using an <ldap:repositories> tag in - your XML configuration or using an @EnableLdapRepositories annotation on a - configuration class. - - - All Spring LDAP repositories must work with entities annotated with the ODM annotations, as described - in . - - - Since all ODM managed classes must have a Distinguished Name as ID, all Spring LDAP repositories must - have the ID type parameter set to javax.naming.Name. Indeed, the built-in - SpringLdapRepository only takes one type parameter; the managed entity class, defaulting - ID to javax.naming.Name. - - - Due to specifics of the LDAP protocol, paging and sorting is not supported for Spring LDAP repositories. - - - - + + Overview + + Spring LDAP has built-in support for Spring Data repositories. The basic functionality and configuration is described here. + When working with Spring LDAP repositories, please note the following: + + + Spring LDAP repositories can be enabled using an <ldap:repositories> tag in + your XML configuration or using an @EnableLdapRepositories annotation on a + configuration class. + + + To include support for LdapQuery parameters in automatically generated repositories, + have your interface extend LdapRepository rather than CrudRepository. + + + All Spring LDAP repositories must work with entities annotated with the ODM annotations, as described + in . + + + Since all ODM managed classes must have a Distinguished Name as ID, all Spring LDAP repositories must + have the ID type parameter set to javax.naming.Name. Indeed, the built-in + SpringLdapRepository only takes one type parameter; the managed entity class, defaulting + ID to javax.naming.Name. + + + Due to specifics of the LDAP protocol, paging and sorting is not supported for Spring LDAP repositories. + + + + + + QueryDSL support + + Basic QueryDSL support is included in Spring LDAP. This support includes the following: + + + An Annotation Processor, LdapAnnotationProcessor, for generating QueryDSL classes + based on Spring LDAP ODM annotations. See for more information on the ODM annotations. + + + A Query implementation, QueryDslLdapQuery, for building and executing QueryDSL + queries in code. + + + Spring Data repository support for QueryDSL predicates. QueryDslPredicateExecutor + includes a number of additional methods with appropriate parameters; extend this interface along with + LdapRepository to include this support in your repository. + + + + diff --git a/test/integration-tests/build.gradle b/test/integration-tests/build.gradle index 093ef533..fed82ee5 100644 --- a/test/integration-tests/build.gradle +++ b/test/integration-tests/build.gradle @@ -3,11 +3,29 @@ repositories { } apply from: JAVA_SCRIPT +idea.module.excludeDirs = [ + file('.gradle'), + file('build/classes'), + file('build/tmp'), + file('build/dependency-cache'), + file('build/libs')] + +sourceSets { + generated { + java { + srcDirs = ['build/generated-src'] + } + } +} + ext.springSecurityVersion='3.0.5.RELEASE' dependencies { compile project(":spring-ldap-test"), - project(":spring-ldap-core-tiger"), + project(":spring-ldap-core-tiger") + + compile "com.mysema.querydsl:querydsl-apt:$queryDslVersion" + compile("org.springframework.security:spring-security-core:$springSecurityVersion") { exclude group: "org.springframework", module: "spring-expression" exclude group: "org.springframework", module: "spring-core" @@ -33,4 +51,38 @@ dependencies { testCompile("org.springframework.security:spring-security-ldap:$springSecurityVersion") { exclude group: "org.springframework.ldap", module: "spring-ldap-core" } -} \ No newline at end of file +} + +task generateQueryDSL(type: JavaCompile, group: 'build', description: 'Generates the QueryDSL query types') { + source = sourceSets.main.java + classpath = configurations.compile + options.compilerArgs = [ + "-proc:only", + "-processor", "org.springframework.ldap.repository.support.LdapAnnotationProcessor" + ] + destinationDir = sourceSets.generated.java.srcDirs.iterator().next() +} + +compileJava { + dependsOn generateQueryDSL + source generateQueryDSL.destinationDir +} + +compileGeneratedJava { + dependsOn generateQueryDSL + options.warnings = false + classpath += sourceSets.main.runtimeClasspath +} + +clean { + delete sourceSets.generated.java.srcDirs +} + +ideaModule.dependsOn generateQueryDSL + +idea { + module { + sourceDirs += file('build/generated-src') + } +} + diff --git a/test/integration-tests/src/main/java/org/springframework/ldap/itest/repositories/PersonQueryDslRepository.java b/test/integration-tests/src/main/java/org/springframework/ldap/itest/repositories/PersonQueryDslRepository.java new file mode 100644 index 00000000..dd78a9c1 --- /dev/null +++ b/test/integration-tests/src/main/java/org/springframework/ldap/itest/repositories/PersonQueryDslRepository.java @@ -0,0 +1,11 @@ +package org.springframework.ldap.itest.repositories; + +import org.springframework.data.querydsl.QueryDslPredicateExecutor; +import org.springframework.ldap.itest.odm.Person; +import org.springframework.ldap.repository.LdapRepository; + +/** + * @author Mattias Hellborg Arthursson + */ +public interface PersonQueryDslRepository extends LdapRepository, QueryDslPredicateExecutor { +} diff --git a/test/integration-tests/src/test/java/org/springframework/ldap/itest/odm/LdapTemplateQueryDslLdapQueryITest.java b/test/integration-tests/src/test/java/org/springframework/ldap/itest/odm/LdapTemplateQueryDslLdapQueryITest.java new file mode 100644 index 00000000..41d01862 --- /dev/null +++ b/test/integration-tests/src/test/java/org/springframework/ldap/itest/odm/LdapTemplateQueryDslLdapQueryITest.java @@ -0,0 +1,69 @@ +/* + * 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.odm; + +import org.junit.Before; +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.repository.support.QueryDslLdapQuery; +import org.springframework.test.context.ContextConfiguration; + +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +/** + * @author Mattias Hellborg Arthursson + */ +@ContextConfiguration(locations = {"/conf/ldapTemplateTestContext.xml"}) +public class LdapTemplateQueryDslLdapQueryITest extends AbstractLdapTemplateIntegrationTest { + @Autowired + private LdapTemplate tested; + private QPerson qperson; + private QueryDslLdapQuery query; + + @Before + public void prepareTestedInstance() { + qperson = QPerson.person; + query = new QueryDslLdapQuery(tested, qperson); + } + + @Test + public void testUniqueResult() { + Person person = query.where(qperson.commonName.eq("Some Person3")).uniqueResult(); + + 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()); + } + + @Test + public void testList() { + List persons = query.where(qperson.commonName.eq("Some Person3")).list(); + + Person person = persons.get(0); + assertEquals("Some Person3", person.getCommonName()); + assertEquals("Person3", person.getSurname()); + assertEquals("Sweden, Company1, Some Person3", person.getDesc().get(0)); + assertEquals("+46 555-123654", person.getTelephoneNumber()); + } +} \ No newline at end of file diff --git a/test/integration-tests/src/test/java/org/springframework/ldap/itest/repository/RepositoryScanQueryDslITest.java b/test/integration-tests/src/test/java/org/springframework/ldap/itest/repository/RepositoryScanQueryDslITest.java new file mode 100644 index 00000000..72b02108 --- /dev/null +++ b/test/integration-tests/src/test/java/org/springframework/ldap/itest/repository/RepositoryScanQueryDslITest.java @@ -0,0 +1,78 @@ +/* + * 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.odm.QPerson; +import org.springframework.ldap.itest.repositories.PersonQueryDslRepository; +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. + * + * @author Mattias Hellborg Arthursson + */ +@ContextConfiguration(locations = {"/conf/repositoryScanTestContext.xml"}) +public class RepositoryScanQueryDslITest extends AbstractLdapTemplateIntegrationTest { + + @Autowired + private PersonQueryDslRepository tested; + + @Test + public void testFindOneWithPredicate() { + QPerson person = QPerson.person; + + Person found = tested.findOne(person.commonName.eq("Some Person3")); + + assertNotNull(found); + assertEquals("Some Person3", found.getCommonName()); + assertEquals("Person3", found.getSurname()); + assertEquals("Sweden, Company1, Some Person3", found.getDesc().get(0)); + assertEquals("+46 555-123654", found.getTelephoneNumber()); + } + + @Test + public void testFindAllWithPredicate() { + QPerson person = QPerson.person; + + Iterable foundPersons = tested.findAll(person.commonName.eq("Some Person3")); + + Person found = foundPersons.iterator().next(); + assertEquals("Some Person3", found.getCommonName()); + assertEquals("Person3", found.getSurname()); + assertEquals("Sweden, Company1, Some Person3", found.getDesc().get(0)); + assertEquals("+46 555-123654", found.getTelephoneNumber()); + } + + @Test + public void testCountWithPredicate() { + QPerson person = QPerson.person; + + long count = tested.count(person.commonName.eq("Some Person3")); + assertEquals(1, count); + } +}