DATAJPA-1170 - Polishing.
Moved implementation code back to Specifications so that it can be made package protected in Lovelace. Replaced simple Specification implementations for composability and negation with static factory methods using lambdas. Added @Deprecated to methods and Specifications type. Polished JavaDoc @deprecated tags. Removed obsolete generics declarations on instantiations. Added JavaDoc to newly added methods in Specification. Original pull request: #211.
This commit is contained in:
@@ -15,17 +15,15 @@
|
||||
*/
|
||||
package org.springframework.data.jpa.domain;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import static org.springframework.data.jpa.domain.Specifications.CompositionType.*;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import javax.persistence.criteria.CriteriaBuilder;
|
||||
import javax.persistence.criteria.CriteriaQuery;
|
||||
import javax.persistence.criteria.Predicate;
|
||||
import javax.persistence.criteria.Root;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import static org.springframework.data.jpa.domain.Specification.CompositionType.*;
|
||||
|
||||
/**
|
||||
* Specification in the sense of Domain Driven Design.
|
||||
*
|
||||
@@ -34,20 +32,33 @@ import static org.springframework.data.jpa.domain.Specification.CompositionType.
|
||||
* @author Krzysztof Rzymkowski
|
||||
* @author Sebastian Staudt
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public interface Specification<T> extends Serializable {
|
||||
|
||||
long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Negates the given {@link Specification}.
|
||||
*
|
||||
* @param <T>
|
||||
* @param spec can be {@literal null}.
|
||||
* @return
|
||||
* @since 2.0
|
||||
*/
|
||||
static <T> Specification<T> not(Specification<T> spec) {
|
||||
return new NegatedSpecification<>(spec);
|
||||
return Specifications.negated(spec);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple static factory method to add some syntactic sugar around a {@link Specification}.
|
||||
*
|
||||
* @param <T>
|
||||
* @param spec can be {@literal null}.
|
||||
* @return
|
||||
* @since 2.0
|
||||
*/
|
||||
static <T> Specification<T> where(Specification<T> spec) {
|
||||
if (spec == null) {
|
||||
return new Specifications<>(null);
|
||||
}
|
||||
|
||||
return spec;
|
||||
return Specifications.where(spec);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -55,9 +66,10 @@ public interface Specification<T> extends Serializable {
|
||||
*
|
||||
* @param other can be {@literal null}.
|
||||
* @return The conjunction of the specifications
|
||||
* @since 2.0
|
||||
*/
|
||||
default Specification<T> and(Specification<T> other) {
|
||||
return new ComposedSpecification<>(this, other, AND);
|
||||
return Specifications.composed(this, other, AND);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -65,9 +77,10 @@ public interface Specification<T> extends Serializable {
|
||||
*
|
||||
* @param other can be {@literal null}.
|
||||
* @return The disjunction of the specifications
|
||||
* @since 2.0
|
||||
*/
|
||||
default Specification<T> or(Specification<T> other) {
|
||||
return new ComposedSpecification<>(this, other, OR);
|
||||
return Specifications.composed(this, other, OR);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -79,100 +92,4 @@ public interface Specification<T> extends Serializable {
|
||||
* @return a {@link Predicate}, may be {@literal null}.
|
||||
*/
|
||||
Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb);
|
||||
|
||||
/**
|
||||
* Enum for the composition types for {@link Predicate}s.
|
||||
*
|
||||
* @author Thomas Darimont
|
||||
*/
|
||||
enum CompositionType {
|
||||
|
||||
AND {
|
||||
@Override
|
||||
public Predicate combine(CriteriaBuilder builder, Predicate lhs, Predicate rhs) {
|
||||
return builder.and(lhs, rhs);
|
||||
}
|
||||
},
|
||||
|
||||
OR {
|
||||
@Override
|
||||
public Predicate combine(CriteriaBuilder builder, Predicate lhs, Predicate rhs) {
|
||||
return builder.or(lhs, rhs);
|
||||
}
|
||||
};
|
||||
|
||||
abstract Predicate combine(CriteriaBuilder builder, Predicate lhs, Predicate rhs);
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link Specification} that negates a given {@code Specification}.
|
||||
*
|
||||
* @author Thomas Darimont
|
||||
* @since 1.6
|
||||
*/
|
||||
class NegatedSpecification<T> implements Specification<T>, Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final Specification<T> spec;
|
||||
|
||||
/**
|
||||
* Creates a new {@link NegatedSpecification} from the given {@link Specification}
|
||||
*
|
||||
* @param spec may be {@literal null}
|
||||
*/
|
||||
NegatedSpecification(Specification<T> spec) {
|
||||
this.spec = spec;
|
||||
}
|
||||
|
||||
public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
|
||||
return spec == null ? null : builder.not(spec.toPredicate(root, query, builder));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link Specification} that combines two given {@code Specification}s via a given {@link CompositionType}.
|
||||
*
|
||||
* @author Thomas Darimont
|
||||
* @since 1.6
|
||||
*/
|
||||
class ComposedSpecification<T> implements Specification<T>, Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final Specification<T> lhs;
|
||||
private final Specification<T> rhs;
|
||||
private final CompositionType compositionType;
|
||||
|
||||
/**
|
||||
* Creates a new {@link ComposedSpecification} from the given {@link Specification} for the left-hand-side and the
|
||||
* right-hand-side with the given {@link CompositionType}.
|
||||
*
|
||||
* @param lhs may be {@literal null}
|
||||
* @param rhs may be {@literal null}
|
||||
* @param compositionType must not be {@literal null}
|
||||
*/
|
||||
ComposedSpecification(Specification<T> lhs, Specification<T> rhs, CompositionType compositionType) {
|
||||
|
||||
Assert.notNull(compositionType, "CompositionType must not be null!");
|
||||
|
||||
this.lhs = lhs;
|
||||
this.rhs = rhs;
|
||||
this.compositionType = compositionType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link Predicate} for the given {@link Root} and {@link CriteriaQuery} that is constructed via the given
|
||||
* {@link CriteriaBuilder}.
|
||||
*/
|
||||
public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
|
||||
|
||||
Predicate otherPredicate = rhs == null ? null : rhs.toPredicate(root, query, builder);
|
||||
Predicate thisPredicate = lhs == null ? null : lhs.toPredicate(root, query, builder);
|
||||
|
||||
return thisPredicate == null ? otherPredicate : otherPredicate == null ? thisPredicate : this.compositionType
|
||||
.combine(builder, thisPredicate, otherPredicate);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2008-2014 the original author or authors.
|
||||
* Copyright 2008-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.
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package org.springframework.data.jpa.domain;
|
||||
|
||||
import static org.springframework.data.jpa.domain.Specifications.CompositionType.*;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import javax.persistence.criteria.CriteriaBuilder;
|
||||
@@ -22,15 +24,15 @@ import javax.persistence.criteria.CriteriaQuery;
|
||||
import javax.persistence.criteria.Predicate;
|
||||
import javax.persistence.criteria.Root;
|
||||
|
||||
import static org.springframework.data.jpa.domain.Specification.CompositionType.*;
|
||||
|
||||
/**
|
||||
* Helper class to easily combine {@link Specification} instances.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Thomas Darimont
|
||||
* @author Sebastian Staudt
|
||||
* @deprecated since 2.0, use factory methods on {@link Specification} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public class Specifications<T> implements Specification<T>, Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
@@ -49,49 +51,53 @@ public class Specifications<T> implements Specification<T>, Serializable {
|
||||
/**
|
||||
* Simple static factory method to add some syntactic sugar around a {@link Specification}.
|
||||
*
|
||||
* @deprecated Use {@link Specification#where} instead
|
||||
* @deprecated since 2.0, use {@link Specification#where} instead
|
||||
* @param <T>
|
||||
* @param spec can be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
@Deprecated
|
||||
public static <T> Specifications<T> where(Specification<T> spec) {
|
||||
return new Specifications<T>(spec);
|
||||
return new Specifications<>(spec);
|
||||
}
|
||||
|
||||
/**
|
||||
* ANDs the given {@link Specification} to the current one.
|
||||
*
|
||||
* @deprecated Use {@link Specification#and} instead
|
||||
* @deprecated since 2.0, use {@link Specification#and} instead
|
||||
* @param <T>
|
||||
* @param other can be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
@Deprecated
|
||||
public Specifications<T> and(Specification<T> other) {
|
||||
return new Specifications<T>(new ComposedSpecification<T>(spec, other, AND));
|
||||
return new Specifications<>(composed(spec, other, AND));
|
||||
}
|
||||
|
||||
/**
|
||||
* ORs the given specification to the current one.
|
||||
*
|
||||
* @deprecated Use {@link Specification#or} instead
|
||||
* @deprecated since 2.0, use {@link Specification#or} instead
|
||||
* @param <T>
|
||||
* @param other can be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
@Deprecated
|
||||
public Specifications<T> or(Specification<T> other) {
|
||||
return new Specifications<T>(new ComposedSpecification<T>(spec, other, OR));
|
||||
return new Specifications<>(composed(spec, other, OR));
|
||||
}
|
||||
|
||||
/**
|
||||
* Negates the given {@link Specification}.
|
||||
*
|
||||
* @deprecated Use {@link Specification#not} instead
|
||||
* @deprecated since 2.0, use {@link Specification#not} instead
|
||||
* @param <T>
|
||||
* @param spec can be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
@Deprecated
|
||||
public static <T> Specifications<T> not(Specification<T> spec) {
|
||||
return new Specifications<T>(new NegatedSpecification<T>(spec));
|
||||
return new Specifications<>(negated(spec));
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -102,4 +108,44 @@ public class Specifications<T> implements Specification<T>, Serializable {
|
||||
return spec == null ? null : spec.toPredicate(root, query, builder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enum for the composition types for {@link Predicate}s. Can not be turned into lambdas as we need to be
|
||||
* serializable.
|
||||
*
|
||||
* @author Thomas Darimont
|
||||
*/
|
||||
enum CompositionType {
|
||||
|
||||
AND {
|
||||
@Override
|
||||
public Predicate combine(CriteriaBuilder builder, Predicate lhs, Predicate rhs) {
|
||||
return builder.and(lhs, rhs);
|
||||
}
|
||||
},
|
||||
|
||||
OR {
|
||||
@Override
|
||||
public Predicate combine(CriteriaBuilder builder, Predicate lhs, Predicate rhs) {
|
||||
return builder.or(lhs, rhs);
|
||||
}
|
||||
};
|
||||
|
||||
abstract Predicate combine(CriteriaBuilder builder, Predicate lhs, Predicate rhs);
|
||||
}
|
||||
|
||||
static <T> Specification<T> negated(Specification<T> spec) {
|
||||
return (root, query, builder) -> spec == null ? null : builder.not(spec.toPredicate(root, query, builder));
|
||||
}
|
||||
|
||||
static <T> Specification<T> composed(Specification<T> lhs, Specification<T> rhs, CompositionType compositionType) {
|
||||
|
||||
return (root, query, builder) -> {
|
||||
|
||||
Predicate otherPredicate = rhs == null ? null : rhs.toPredicate(root, query, builder);
|
||||
Predicate thisPredicate = lhs == null ? null : lhs.toPredicate(root, query, builder);
|
||||
|
||||
return thisPredicate == null ? otherPredicate
|
||||
: otherPredicate == null ? thisPredicate : compositionType.combine(builder, thisPredicate, otherPredicate);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,8 +17,8 @@ package org.springframework.data.jpa.domain;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.springframework.data.jpa.domain.Specification.*;
|
||||
import static org.springframework.data.jpa.domain.Specification.not;
|
||||
import static org.springframework.data.jpa.domain.Specification.where;
|
||||
import static org.springframework.util.SerializationUtils.*;
|
||||
|
||||
import java.io.Serializable;
|
||||
@@ -35,10 +35,13 @@ import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link Specification}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Thomas Darimont
|
||||
* @author Sebastian Staudt
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class SpecificationUnitTests implements Serializable {
|
||||
|
||||
@@ -50,13 +53,12 @@ public class SpecificationUnitTests implements Serializable {
|
||||
@Mock(extraInterfaces = Serializable.class) Predicate predicate;
|
||||
|
||||
@Before
|
||||
@SuppressWarnings("unchecked")
|
||||
public void setUp() {
|
||||
|
||||
spec = (root, query, cb) -> predicate;
|
||||
}
|
||||
|
||||
@Test // DATAJPA-300
|
||||
@Test // DATAJPA-300, DATAJPA-1170
|
||||
public void createsSpecificationsFromNull() {
|
||||
|
||||
Specification<Object> specification = where(null);
|
||||
@@ -64,7 +66,7 @@ public class SpecificationUnitTests implements Serializable {
|
||||
assertThat(specification.toPredicate(root, query, builder), is(nullValue()));
|
||||
}
|
||||
|
||||
@Test // DATAJPA-300
|
||||
@Test // DATAJPA-300, DATAJPA-1170
|
||||
public void negatesNullSpecToNull() {
|
||||
|
||||
Specification<Object> specification = not(null);
|
||||
@@ -73,7 +75,7 @@ public class SpecificationUnitTests implements Serializable {
|
||||
assertThat(specification.toPredicate(root, query, builder), is(nullValue()));
|
||||
}
|
||||
|
||||
@Test // DATAJPA-300
|
||||
@Test // DATAJPA-300, DATAJPA-1170
|
||||
public void andConcatenatesSpecToNullSpec() {
|
||||
|
||||
Specification<Object> specification = where(null);
|
||||
@@ -83,7 +85,7 @@ public class SpecificationUnitTests implements Serializable {
|
||||
assertThat(specification.toPredicate(root, query, builder), is(predicate));
|
||||
}
|
||||
|
||||
@Test // DATAJPA-300
|
||||
@Test // DATAJPA-300, DATAJPA-1170
|
||||
public void andConcatenatesNullSpecToSpec() {
|
||||
|
||||
Specification<Object> specification = spec.and(null);
|
||||
@@ -92,7 +94,7 @@ public class SpecificationUnitTests implements Serializable {
|
||||
assertThat(specification.toPredicate(root, query, builder), is(predicate));
|
||||
}
|
||||
|
||||
@Test // DATAJPA-300
|
||||
@Test // DATAJPA-300, DATAJPA-1170
|
||||
public void orConcatenatesSpecToNullSpec() {
|
||||
|
||||
Specification<Object> specification = where(null);
|
||||
@@ -102,7 +104,7 @@ public class SpecificationUnitTests implements Serializable {
|
||||
assertThat(specification.toPredicate(root, query, builder), is(predicate));
|
||||
}
|
||||
|
||||
@Test // DATAJPA-300
|
||||
@Test // DATAJPA-300, DATAJPA-1170
|
||||
public void orConcatenatesNullSpecToSpec() {
|
||||
|
||||
Specification<Object> specification = spec.or(null);
|
||||
@@ -129,7 +131,8 @@ public class SpecificationUnitTests implements Serializable {
|
||||
public void complexSpecificationsShouldBeSerializable() {
|
||||
|
||||
SerializableSpecification serializableSpec = new SerializableSpecification();
|
||||
Specification<Object> specification = Specification.not(serializableSpec.and(serializableSpec).or(serializableSpec));
|
||||
Specification<Object> specification = Specification
|
||||
.not(serializableSpec.and(serializableSpec).or(serializableSpec));
|
||||
|
||||
assertThat(specification, is(notNullValue()));
|
||||
|
||||
@@ -140,10 +143,10 @@ public class SpecificationUnitTests implements Serializable {
|
||||
}
|
||||
|
||||
public class SerializableSpecification implements Serializable, Specification<Object> {
|
||||
|
||||
@Override
|
||||
public Predicate toPredicate(Root<Object> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user