diff --git a/src/main/java/org/springframework/data/jpa/domain/Specifications.java b/src/main/java/org/springframework/data/jpa/domain/Specifications.java index 4dce73921..c6c933402 100644 --- a/src/main/java/org/springframework/data/jpa/domain/Specifications.java +++ b/src/main/java/org/springframework/data/jpa/domain/Specifications.java @@ -20,8 +20,6 @@ import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; -import org.springframework.util.Assert; - /** * Helper class to easily combine {@link Specification} instances. * @@ -29,14 +27,12 @@ import org.springframework.util.Assert; */ public class Specifications implements Specification { - private static final String NOT_NULL = "Specification given to %s(…) must not be null!"; - private final Specification spec; /** * Creates a new {@link Specifications} wrapper for the given {@link Specification}. * - * @param spec must not be {@literal null}. + * @param spec can be {@literal null}. */ private Specifications(Specification spec) { this.spec = spec; @@ -46,12 +42,10 @@ public class Specifications implements Specification { * Simple static factory method to add some syntactic sugar around a {@link Specification}. * * @param - * @param spec must not be {@literal null}. + * @param spec can be {@literal null}. * @return */ public static Specifications where(Specification spec) { - - Assert.notNull(spec, String.format(NOT_NULL, "where")); return new Specifications(spec); } @@ -59,16 +53,19 @@ public class Specifications implements Specification { * ANDs the given {@link Specification} to the current one. * * @param - * @param other must not be {@literal null}. + * @param other can be {@literal null}. * @return */ public Specifications and(final Specification other) { - Assert.notNull(spec, String.format(NOT_NULL, "and")); - return new Specifications(new Specification() { public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder builder) { - return builder.and(spec.toPredicate(root, query, builder), other.toPredicate(root, query, builder)); + + Predicate otherPredicate = other == null ? null : other.toPredicate(root, query, builder); + Predicate thisPredicate = spec == null ? null : spec.toPredicate(root, query, builder); + + return thisPredicate == null ? otherPredicate : otherPredicate == null ? thisPredicate : builder.and( + thisPredicate, otherPredicate); } }); } @@ -77,16 +74,19 @@ public class Specifications implements Specification { * ORs the given specification to the current one. * * @param - * @param other must not be {@literal null}. + * @param other can be {@literal null}. * @return */ public Specifications or(final Specification other) { - Assert.notNull(spec, String.format(NOT_NULL, "or")); - return new Specifications(new Specification() { public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder builder) { - return builder.or(spec.toPredicate(root, query, builder), other.toPredicate(root, query, builder)); + + Predicate otherPredicate = other == null ? null : other.toPredicate(root, query, builder); + Predicate thisPredicate = spec == null ? null : spec.toPredicate(root, query, builder); + + return thisPredicate == null ? otherPredicate : otherPredicate == null ? thisPredicate : builder.or( + thisPredicate, otherPredicate); } }); } @@ -95,16 +95,13 @@ public class Specifications implements Specification { * Negates the given {@link Specification}. * * @param - * @param spec must not be {@literal null}. + * @param spec can be {@literal null}. * @return */ public static Specifications not(final Specification spec) { - - Assert.notNull(spec, String.format(NOT_NULL, "not")); - return new Specifications(new Specification() { public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder builder) { - return builder.not(spec.toPredicate(root, query, builder)); + return spec == null ? null : builder.not(spec.toPredicate(root, query, builder)); } }); } @@ -114,6 +111,6 @@ public class Specifications implements Specification { * @see org.springframework.data.jpa.domain.Specification#toPredicate(javax.persistence.criteria.Root, javax.persistence.criteria.CriteriaQuery, javax.persistence.criteria.CriteriaBuilder) */ public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder builder) { - return spec.toPredicate(root, query, builder); + return spec == null ? null : spec.toPredicate(root, query, builder); } } diff --git a/src/test/java/org/springframework/data/jpa/domain/SpecificationsUnitTests.java b/src/test/java/org/springframework/data/jpa/domain/SpecificationsUnitTests.java new file mode 100644 index 000000000..4b0e5e653 --- /dev/null +++ b/src/test/java/org/springframework/data/jpa/domain/SpecificationsUnitTests.java @@ -0,0 +1,131 @@ +/* + * Copyright 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.data.jpa.domain; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; +import static org.springframework.data.jpa.domain.Specifications.*; + +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Predicate; +import javax.persistence.criteria.Root; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +/** + * @author Oliver Gierke + */ +@RunWith(MockitoJUnitRunner.class) +public class SpecificationsUnitTests { + + @Mock + Specification mockSpec; + @Mock + Root root; + @Mock + CriteriaQuery query; + @Mock + CriteriaBuilder builder; + + @Mock + Predicate predicate; + + @Before + public void setUp() { + when(mockSpec.toPredicate(root, query, builder)).thenReturn(predicate); + } + + /** + * @see DATAJPA-300 + */ + @Test + public void createsSpecificationsFromNull() { + + Specifications specification = where(null); + assertThat(specification, is(notNullValue())); + assertThat(specification.toPredicate(root, query, builder), is(nullValue())); + } + + /** + * @see DATAJPA-300 + */ + @Test + public void negatesNullSpecToNull() { + + Specifications specification = not((Specification) null); + + assertThat(specification, is(notNullValue())); + assertThat(specification.toPredicate(root, query, builder), is(nullValue())); + } + + /** + * @see DATAJPA-300 + */ + @Test + public void andConcatenatesSpecToNullSpec() { + + Specifications specification = where(null); + specification = specification.and(mockSpec); + + assertThat(specification, is(notNullValue())); + assertThat(specification.toPredicate(root, query, builder), is(predicate)); + } + + /** + * @see DATAJPA-300 + */ + @Test + public void andConcatenatesNullSpecToSpec() { + + Specifications specification = where(mockSpec); + specification = specification.and(null); + + assertThat(specification, is(notNullValue())); + assertThat(specification.toPredicate(root, query, builder), is(predicate)); + } + + /** + * @see DATAJPA-300 + */ + @Test + public void orConcatenatesSpecToNullSpec() { + + Specifications specification = where(null); + specification = specification.or(mockSpec); + + assertThat(specification, is(notNullValue())); + assertThat(specification.toPredicate(root, query, builder), is(predicate)); + } + + /** + * @see DATAJPA-300 + */ + @Test + public void orConcatenatesNullSpecToSpec() { + + Specifications specification = where(mockSpec); + specification = specification.or(null); + + assertThat(specification, is(notNullValue())); + assertThat(specification.toPredicate(root, query, builder), is(predicate)); + } +}