DATALDAP-45 - Introduced usage of nullable annotations for API validation.

Mark all packages with Spring Frameworks @NonNullApi. Add Spring's @Nullable to methods, parameters and fields that take or produce null values. Adapt using code to make sure the IDE can evaluate the null flow properly. Introduce methods that return required (non-null) values.
This commit is contained in:
Mark Paluch
2017-07-28 10:46:12 +02:00
parent 1a10d2ffa1
commit b3f1bbc882
19 changed files with 197 additions and 59 deletions

View File

@@ -1,6 +1,10 @@
[[new-features]]
= New & Noteworthy
[[new-features.2.0]]
== What's new in Spring Data LDAP 2.0
* Enhanced tooling support by using Spring Framework's `@NonNullApi` and `@Nullable` annotations.
[[new-features.1.0]]
== What's new in Spring Data LDAP 1.0
* Migration of Spring LDAP's repository suport into Spring Data LDAP.
* Migration of Spring LDAP's repository support into Spring Data LDAP.

View File

@@ -0,0 +1,7 @@
/**
* XML configuration support for Spring Data LDAP repositories.
*/
@NonNullApi
package org.springframework.data.ldap.config;
import org.springframework.lang.NonNullApi;

View File

@@ -0,0 +1,7 @@
/**
* Support infrastructure for the configuration of LDAP specific repositories.
*/
@NonNullApi
package org.springframework.data.ldap.repository.config;
import org.springframework.lang.NonNullApi;

View File

@@ -38,7 +38,7 @@ public abstract class AbstractLdapRepositoryQuery implements RepositoryQuery {
/**
* Creates a new {@link AbstractLdapRepositoryQuery} instance given {@link LdapQuery}, {@link Class} and
* {@link LdapOperations}.
*
*
* @param queryMethod must not be {@literal null}.
* @param entityType must not be {@literal null}.
* @param ldapOperations must not be {@literal null}.
@@ -58,6 +58,7 @@ public abstract class AbstractLdapRepositoryQuery implements RepositoryQuery {
* @see org.springframework.data.repository.query.RepositoryQuery#execute(java.lang.Object[])
*/
@Override
@SuppressWarnings("ConstantConditions")
public final Object execute(Object[] parameters) {
LdapQuery query = createQuery(parameters);
@@ -75,7 +76,7 @@ public abstract class AbstractLdapRepositoryQuery implements RepositoryQuery {
/**
* Creates a {@link Query} instance using the given {@literal parameters}.
*
*
* @param parameters must not be {@literal null}.
* @return
*/

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2016 the original author or authors.
* Copyright 2016-2017 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.
@@ -26,6 +26,7 @@ import org.springframework.util.Assert;
* Handles queries for repository methods annotated with {@link org.springframework.data.ldap.repository.Query}.
*
* @author Mattias Hellborg Arthursson
* @author Mark Paluch
*/
public class AnnotatedLdapRepositoryQuery extends AbstractLdapRepositoryQuery {
@@ -33,7 +34,7 @@ public class AnnotatedLdapRepositoryQuery extends AbstractLdapRepositoryQuery {
/**
* Construct a new instance.
*
*
* @param queryMethod the QueryMethod.
* @param entityType the managed class.
* @param ldapOperations the LdapOperations instance to use.
@@ -45,7 +46,7 @@ public class AnnotatedLdapRepositoryQuery extends AbstractLdapRepositoryQuery {
Assert.notNull(queryMethod.getQueryAnnotation(), "Annotation must be present");
Assert.hasLength(queryMethod.getQueryAnnotation().value(), "Query filter must be specified");
queryAnnotation = queryMethod.getQueryAnnotation();
queryAnnotation = queryMethod.getRequiredQueryAnnotation();
}
/* (non-Javadoc)

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2016 the original author or authors.
* Copyright 2016-2017 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.
@@ -19,6 +19,7 @@ import static org.springframework.ldap.query.LdapQueryBuilder.*;
import java.util.Iterator;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.data.domain.Sort;
import org.springframework.data.mapping.PropertyPath;
import org.springframework.data.repository.query.Parameters;
@@ -31,6 +32,7 @@ import org.springframework.ldap.odm.core.ObjectDirectoryMapper;
import org.springframework.ldap.query.ConditionCriteria;
import org.springframework.ldap.query.ContainerCriteria;
import org.springframework.ldap.query.LdapQuery;
import org.springframework.ldap.query.LdapQueryBuilder;
import org.springframework.util.Assert;
/**
@@ -71,8 +73,15 @@ class LdapQueryCreator extends AbstractQueryCreator<LdapQuery, ContainerCriteria
@Override
protected ContainerCriteria create(Part part, Iterator<Object> iterator) {
String base = entityType.getAnnotation(Entry.class).base();
ConditionCriteria criteria = query().base(base).where(getAttribute(part));
Entry entry = AnnotatedElementUtils.findMergedAnnotation(entityType, Entry.class);
LdapQueryBuilder query = query();
if (entry != null) {
query = query.base(entry.base());
}
ConditionCriteria criteria = query.where(getAttribute(part));
return appendCondition(part, iterator, criteria);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2016 the original author or authors.
* Copyright 2016-2017 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.
@@ -22,12 +22,14 @@ import org.springframework.data.ldap.repository.Query;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.query.QueryMethod;
import org.springframework.lang.Nullable;
/**
* QueryMethod for Ldap Queries.
*
* @author Mattias Hellborg Arthursson
* @author Eddu Melendez
* @author Mark Paluch
*/
public class LdapQueryMethod extends QueryMethod {
@@ -48,7 +50,7 @@ public class LdapQueryMethod extends QueryMethod {
/**
* Check whether the target method is annotated with {@link org.springframework.data.ldap.repository.Query}.
*
*
* @return <code>true</code> if the target method is annotated with
* {@link org.springframework.data.ldap.repository.Query}, <code>false</code> otherwise.
*/
@@ -58,11 +60,29 @@ public class LdapQueryMethod extends QueryMethod {
/**
* Get the {@link org.springframework.data.ldap.repository.Query} annotation of the target method (if any).
*
*
* @return the {@link org.springframework.data.ldap.repository.Query} annotation of the target method if present, or
* <code>null</code> otherwise.
*/
@Nullable
Query getQueryAnnotation() {
return AnnotationUtils.getAnnotation(method, Query.class);
}
/**
* Get the required {@link org.springframework.data.ldap.repository.Query} annotation of the target method.
*
* @return the {@link org.springframework.data.ldap.repository.Query} annotation of the target method if present, or
* {@link IllegalStateException} otherwise.
*/
Query getRequiredQueryAnnotation() {
Query queryAnnotation = getQueryAnnotation();
if (queryAnnotation != null) {
return queryAnnotation;
}
throw new IllegalStateException("Required @Query annotation is not present!");
}
}

View File

@@ -0,0 +1,7 @@
/**
* Query derivation mechanism for LDAP specific repositories.
*/
@NonNullApi
package org.springframework.data.ldap.repository.query;
import org.springframework.lang.NonNullApi;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2016 the original author or authors.
* Copyright 2016-2017 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.
@@ -22,22 +22,24 @@ import java.util.Map;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.VariableElement;
import org.springframework.lang.Nullable;
import org.springframework.ldap.odm.annotations.Id;
import com.querydsl.apt.DefaultConfiguration;
/**
* Configuration for {@link LdapAnnotationProcessor}.
*
*
* @author Mattias Hellborg Arthursson
* @author Eddu Melendez
* @author Mark Paluch
*/
class DefaultLdapAnnotationProcessorConfiguration extends DefaultConfiguration {
public DefaultLdapAnnotationProcessorConfiguration(RoundEnvironment roundEnv, Map<String, String> options,
Collection<String> keywords, Class<? extends Annotation> entitiesAnn, Class<? extends Annotation> entityAnn,
Class<? extends Annotation> superTypeAnn, Class<? extends Annotation> embeddableAnn,
Class<? extends Annotation> embeddedAnn, Class<? extends Annotation> skipAnn) {
@Nullable Class<? extends Annotation> superTypeAnn, @Nullable Class<? extends Annotation> embeddableAnn,
@Nullable Class<? extends Annotation> embeddedAnn, Class<? extends Annotation> skipAnn) {
super(roundEnv, options, keywords, entitiesAnn, entityAnn, superTypeAnn, embeddableAnn, embeddedAnn, skipAnn);
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright 2017 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.data.ldap.repository.support;
import javax.naming.Name;
import org.springframework.data.repository.core.support.AbstractEntityInformation;
import org.springframework.lang.Nullable;
import org.springframework.ldap.odm.core.impl.DefaultObjectDirectoryMapper;
/**
* ODM-based {@link org.springframework.data.repository.core.EntityInformation} for LDAP entities.
*
* @author Mark Paluch
* @since 2.0
* @see org.springframework.ldap.odm.core.ObjectDirectoryMapper
* @see org.springframework.ldap.odm.annotations.Entry
*/
class LdapEntityInformation<T> extends AbstractEntityInformation<T, Name> {
private final DefaultObjectDirectoryMapper MAPPER = new DefaultObjectDirectoryMapper();
public LdapEntityInformation(Class<T> domainClass) {
super(domainClass);
}
@Nullable
@Override
public Name getId(T entity) {
return MAPPER.getId(entity);
}
@Override
public Class<Name> getIdType() {
return Name.class;
}
}

View File

@@ -17,7 +17,6 @@ package org.springframework.data.ldap.repository.support;
import static org.springframework.data.querydsl.QuerydslUtils.*;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.Optional;
@@ -35,6 +34,7 @@ import org.springframework.data.repository.query.EvaluationContextProvider;
import org.springframework.data.repository.query.QueryLookupStrategy;
import org.springframework.data.repository.query.QueryLookupStrategy.Key;
import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.lang.Nullable;
import org.springframework.ldap.core.LdapOperations;
import org.springframework.util.Assert;
@@ -68,8 +68,9 @@ public class LdapRepositoryFactory extends RepositoryFactorySupport {
* @see org.springframework.data.repository.core.support.RepositoryFactorySupport#getEntityInformation(java.lang.Class)
*/
@Override
@SuppressWarnings("unchecked")
public <T, ID> EntityInformation<T, ID> getEntityInformation(Class<T> domainClass) {
return null;
return new LdapEntityInformation(domainClass);
}
/*
@@ -99,7 +100,7 @@ public class LdapRepositoryFactory extends RepositoryFactorySupport {
* @see org.springframework.data.repository.core.support.RepositoryFactorySupport#getQueryLookupStrategy(org.springframework.data.repository.query.QueryLookupStrategy.Key, org.springframework.data.repository.query.EvaluationContextProvider)
*/
@Override
protected Optional<QueryLookupStrategy> getQueryLookupStrategy(Key key,
protected Optional<QueryLookupStrategy> getQueryLookupStrategy(@Nullable Key key,
EvaluationContextProvider evaluationContextProvider) {
return Optional.of(queryLookupStrategy);
}
@@ -109,9 +110,9 @@ public class LdapRepositoryFactory extends RepositoryFactorySupport {
private LdapOperations ldapOperations;
/**
* @param ldapOperations
* @param ldapOperations must not be {@literal null}.
*/
public LdapQueryLookupStrategy(LdapOperations ldapOperations) {
LdapQueryLookupStrategy(LdapOperations ldapOperations) {
this.ldapOperations = ldapOperations;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2016 the original author or authors.
* Copyright 2016-2017 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.
@@ -20,6 +20,7 @@ import javax.naming.Name;
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.lang.Nullable;
import org.springframework.ldap.core.LdapOperations;
import org.springframework.util.Assert;
@@ -29,15 +30,16 @@ import org.springframework.util.Assert;
*
* @author Mattias Hellborg Arthursson
* @author Oliver Gierke
* @author Mark Paluch
*/
public class LdapRepositoryFactoryBean<T extends Repository<S, Name>, S>
extends RepositoryFactoryBeanSupport<T, S, Name> {
private LdapOperations ldapOperations;
private @Nullable LdapOperations ldapOperations;
/**
* Creates a new {@link LdapRepositoryFactoryBean} for the given repository interface.
*
*
* @param repositoryInterface must not be {@literal null}.
*/
public LdapRepositoryFactoryBean(Class<? extends T> repositoryInterface) {
@@ -54,6 +56,9 @@ public class LdapRepositoryFactoryBean<T extends Repository<S, Name>, S>
*/
@Override
protected RepositoryFactorySupport createRepositoryFactory() {
Assert.state(ldapOperations != null, "LdapOperations must be set");
return new LdapRepositoryFactory(ldapOperations);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2016 the original author or authors.
* Copyright 2016-2017 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.
@@ -41,7 +41,7 @@ class LdapSerializer implements Visitor<Object, Void> {
/**
* Creates a new {@link LdapSerializer}.
*
*
* @param odm
* @param entityType
*/

View File

@@ -42,8 +42,7 @@ public class QuerydslLdapQuery<K> implements FilteredClause<QuerydslLdapQuery<K>
private final Class<? extends K> entityType;
private final LdapSerializer filterGenerator;
private QueryMixin<QuerydslLdapQuery<K>> queryMixin = new QueryMixin<>(this,
new DefaultQueryMetadata().noValidate());
private QueryMixin<QuerydslLdapQuery<K>> queryMixin = new QueryMixin<>(this, new DefaultQueryMetadata().noValidate());
/**
* Creates a new {@link QuerydslLdapQuery}.
@@ -88,8 +87,13 @@ public class QuerydslLdapQuery<K> implements FilteredClause<QuerydslLdapQuery<K>
return ldapOperations.findOne(buildQuery(), entityType);
}
LdapQuery buildQuery() {
return query().filter(filterGenerator.handle(queryMixin.getMetadata().getWhere()));
}
private LdapQuery buildQuery() {
Predicate where = queryMixin.getMetadata().getWhere();
if (where != null) {
return query().filter(filterGenerator.handle(where));
}
return query();
}
}

View File

@@ -28,6 +28,7 @@ import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.data.domain.Persistable;
import org.springframework.data.ldap.repository.LdapRepository;
import org.springframework.data.util.Optionals;
import org.springframework.lang.Nullable;
import org.springframework.ldap.NameNotFoundException;
import org.springframework.ldap.core.LdapOperations;
import org.springframework.ldap.core.support.CountNameClassPairCallbackHandler;
@@ -83,7 +84,7 @@ public class SimpleLdapRepository<T> implements LdapRepository<T> {
return callback.getNoOfRows();
}
private <S extends T> boolean isNew(S entity, Name id) {
private <S extends T> boolean isNew(S entity, @Nullable Name id) {
if (entity instanceof Persistable) {
Persistable<?> persistable = (Persistable<?>) entity;
@@ -171,7 +172,7 @@ public class SimpleLdapRepository<T> implements LdapRepository<T> {
Assert.notNull(name, "Id must not be null");
return findById(name) != null;
return findById(name).isPresent();
}
/* (non-Javadoc)

View File

@@ -0,0 +1,7 @@
/**
* Support infrastructure for query derivation of LDAP specific repositories.
*/
@NonNullApi
package org.springframework.data.ldap.repository.support;
import org.springframework.lang.NonNullApi;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2016 the original author or authors.
* Copyright 2016-2017 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.
@@ -34,7 +34,7 @@ import org.springframework.test.context.junit4.SpringRunner;
/**
* Unit tests for {@link EnableLdapRepositories#repositoryBaseClass()}.
*
*
* @author Mark Paluch
*/
@RunWith(SpringRunner.class)

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2016 the original author or authors.
* Copyright 2016-2017 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.
@@ -19,7 +19,6 @@ import static org.assertj.core.api.Assertions.*;
import java.lang.reflect.Method;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.ldap.repository.support.BaseUnitTestPerson;
@@ -35,23 +34,17 @@ import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;
/**
* @author Mattias Hellborg Arthursson
* @author Eddu Melendez
* @author Mark Paluch
*/
@ContextConfiguration
public class PartTreeLdapRepositoryQueryTests extends AbstractJUnit4SpringContextTests {
@Autowired private LdapTemplate ldapTemplate;
private Class<?> targetClass;
private Class<?> entityClass;
private DefaultRepositoryMetadata repositoryMetadata;
private ProjectionFactory factory;
@Before
public void prepareTest() {
entityClass = UnitTestPerson.class;
targetClass = UnitTestPersonRepository.class;
repositoryMetadata = new DefaultRepositoryMetadata(targetClass);
factory = new SpelAwareProxyProjectionFactory();
}
private Class<?> entityClass = UnitTestPerson.class;
private Class<?> targetClass = UnitTestPersonRepository.class;
private DefaultRepositoryMetadata repositoryMetadata = new DefaultRepositoryMetadata(targetClass);
private ProjectionFactory factory = new SpelAwareProxyProjectionFactory();
@Test
public void testFindByFullName() throws NoSuchMethodException {
@@ -61,9 +54,11 @@ public class PartTreeLdapRepositoryQueryTests extends AbstractJUnit4SpringContex
// LDAP-314
@Test
public void testFindByFullNameWithBase() throws NoSuchMethodException {
entityClass = BaseUnitTestPerson.class;
targetClass = BaseTestPersonRepository.class;
repositoryMetadata = new DefaultRepositoryMetadata(targetClass);
assertFilterAndBaseForMethod(targetClass.getMethod("findByFullName", String.class), "(cn=John Doe)", "ou=someOu",
"John Doe");
}
@@ -136,6 +131,7 @@ public class PartTreeLdapRepositoryQueryTests extends AbstractJUnit4SpringContex
private void assertFilterAndBaseForMethod(Method targetMethod, String expectedFilter, String expectedBase,
Object... expectedParams) {
LdapQueryMethod queryMethod = new LdapQueryMethod(targetMethod, repositoryMetadata, factory);
PartTreeLdapRepositoryQuery tested = new PartTreeLdapRepositoryQuery(queryMethod, entityClass, ldapTemplate);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2016 the original author or authors.
* Copyright 2016-2017 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.
@@ -17,7 +17,6 @@ package org.springframework.data.ldap.repository.support;
import static org.assertj.core.api.Assertions.*;
import org.junit.Before;
import org.junit.Test;
import org.springframework.ldap.filter.Filter;
import org.springframework.ldap.odm.core.ObjectDirectoryMapper;
@@ -31,92 +30,108 @@ import com.querydsl.core.types.Expression;
*/
public class QuerydslFilterGeneratorTests {
private LdapSerializer tested;
private QPerson person;
@Before
public void prepareTestedInstance() {
ObjectDirectoryMapper odm = new DefaultObjectDirectoryMapper();
tested = new LdapSerializer(odm, UnitTestPerson.class);
person = QPerson.person;
}
private ObjectDirectoryMapper odm = new DefaultObjectDirectoryMapper();
private LdapSerializer tested = new LdapSerializer(odm, UnitTestPerson.class);
private QPerson person = QPerson.person;
@Test
public void testEqualsFilter() {
Expression<?> expression = person.fullName.eq("John Doe");
Filter result = tested.handle(expression);
assertThat(result.toString()).isEqualTo("(cn=John Doe)");
}
@Test
public void testAndFilter() {
Expression<?> expression = person.fullName.eq("John Doe").and(person.lastName.eq("Doe"));
Filter result = tested.handle(expression);
assertThat(result.toString()).isEqualTo("(&(cn=John Doe)(sn=Doe))");
}
@Test
public void testOrFilter() {
Expression<?> expression = person.fullName.eq("John Doe").or(person.lastName.eq("Doe"));
Filter result = tested.handle(expression);
assertThat(result.toString()).isEqualTo("(|(cn=John Doe)(sn=Doe))");
}
@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);
assertThat(result.toString()).isEqualTo("(&(cn=John Doe)(|(sn=Doe)(sn=Die)))");
}
@Test
public void testNot() {
Expression<?> expression = person.fullName.eq("John Doe").not();
Filter result = tested.handle(expression);
assertThat(result.toString()).isEqualTo("(!(cn=John Doe))");
}
@Test
public void testIsLike() {
Expression<?> expression = person.fullName.like("kalle*");
Filter result = tested.handle(expression);
assertThat(result.toString()).isEqualTo("(cn=kalle*)");
}
@Test
public void testStartsWith() {
Expression<?> expression = person.fullName.startsWith("kalle");
Filter result = tested.handle(expression);
assertThat(result.toString()).isEqualTo("(cn=kalle*)");
}
@Test
public void testEndsWith() {
Expression<?> expression = person.fullName.endsWith("kalle");
Filter result = tested.handle(expression);
assertThat(result.toString()).isEqualTo("(cn=*kalle)");
}
@Test
public void testContains() {
Expression<?> expression = person.fullName.contains("kalle");
Filter result = tested.handle(expression);
assertThat(result.toString()).isEqualTo("(cn=*kalle*)");
}
@Test
public void testNotNull() {
Expression<?> expression = person.fullName.isNotNull();
Filter result = tested.handle(expression);
assertThat(result.toString()).isEqualTo("(cn=*)");
}
@Test
public void testNull() {
Expression<?> expression = person.fullName.isNull();
Filter result = tested.handle(expression);
assertThat(result.toString()).isEqualTo("(!(cn=*))");
}
}