DATAJPA-523 - Make Specifications implement Serializable.
Specifications now implements Serializable to be able to transfer them across systems boundaries. Introduced special Serializable Specification types for composed and negated Specifications. Note that currently only Hibernate and EclipseLink expression structures implement Serializable - OpenJPA's don't. Original pull request: #86.
This commit is contained in:
committed by
Oliver Gierke
parent
e7f0f190d1
commit
caad3db37f
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2008-2011 the original author or authors.
|
||||
* Copyright 2008-2014 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.
|
||||
@@ -24,6 +24,7 @@ import javax.persistence.criteria.Root;
|
||||
* Specification in the sense of Domain Driven Design.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Thomas Darimont
|
||||
*/
|
||||
public interface Specification<T> {
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2008-2012 the original author or authors.
|
||||
* Copyright 2008-2014 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,17 +15,26 @@
|
||||
*/
|
||||
package org.springframework.data.jpa.domain;
|
||||
|
||||
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 org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Helper class to easily combine {@link Specification} instances.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Thomas Darimont
|
||||
*/
|
||||
public class Specifications<T> implements Specification<T> {
|
||||
public class Specifications<T> implements Specification<T>, Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final Specification<T> spec;
|
||||
|
||||
@@ -56,18 +65,8 @@ public class Specifications<T> implements Specification<T> {
|
||||
* @param other can be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public Specifications<T> and(final Specification<T> other) {
|
||||
|
||||
return new Specifications<T>(new Specification<T>() {
|
||||
public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder 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);
|
||||
}
|
||||
});
|
||||
public Specifications<T> and(Specification<T> other) {
|
||||
return new Specifications<T>(new ComposedSpecification<T>(spec, other, AND));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -77,18 +76,8 @@ public class Specifications<T> implements Specification<T> {
|
||||
* @param other can be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public Specifications<T> or(final Specification<T> other) {
|
||||
|
||||
return new Specifications<T>(new Specification<T>() {
|
||||
public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder 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);
|
||||
}
|
||||
});
|
||||
public Specifications<T> or(Specification<T> other) {
|
||||
return new Specifications<T>(new ComposedSpecification<T>(spec, other, OR));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -98,12 +87,8 @@ public class Specifications<T> implements Specification<T> {
|
||||
* @param spec can be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static <T> Specifications<T> not(final Specification<T> spec) {
|
||||
return new Specifications<T>(new Specification<T>() {
|
||||
public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
|
||||
return spec == null ? null : builder.not(spec.toPredicate(root, query, builder));
|
||||
}
|
||||
});
|
||||
public static <T> Specifications<T> not(Specification<T> spec) {
|
||||
return new Specifications<T>(new NegatedSpecification<T>(spec));
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -113,4 +98,99 @@ public class Specifications<T> implements Specification<T> {
|
||||
public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
|
||||
return spec == null ? null : spec.toPredicate(root, query, builder);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
private static 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 {@iteral null}
|
||||
*/
|
||||
public 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
|
||||
*/
|
||||
private static 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}
|
||||
*/
|
||||
private 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 2013 the original author or authors.
|
||||
* Copyright 2013-2014 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,9 @@ import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.springframework.data.jpa.domain.Specifications.*;
|
||||
import static org.springframework.util.SerializationUtils.*;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import javax.persistence.criteria.CriteriaBuilder;
|
||||
import javax.persistence.criteria.CriteriaQuery;
|
||||
@@ -33,24 +36,24 @@ import org.mockito.runners.MockitoJUnitRunner;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
* @author Thomas Darimont
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class SpecificationsUnitTests {
|
||||
|
||||
@Mock
|
||||
Specification<Object> mockSpec;
|
||||
@Mock
|
||||
Root<Object> root;
|
||||
@Mock
|
||||
CriteriaQuery<?> query;
|
||||
@Mock
|
||||
CriteriaBuilder builder;
|
||||
@Mock(extraInterfaces = Serializable.class) Root<Object> root;
|
||||
@Mock(extraInterfaces = Serializable.class) CriteriaQuery<?> query;
|
||||
@Mock(extraInterfaces = Serializable.class) CriteriaBuilder builder;
|
||||
|
||||
@Mock
|
||||
Predicate predicate;
|
||||
@Mock(extraInterfaces = Serializable.class) Predicate predicate;
|
||||
|
||||
@Before
|
||||
@SuppressWarnings("unchecked")
|
||||
public void setUp() {
|
||||
|
||||
mockSpec = (Specification<Object>) mock(Specification.class, withSettings().serializable());
|
||||
|
||||
when(mockSpec.toPredicate(root, query, builder)).thenReturn(predicate);
|
||||
}
|
||||
|
||||
@@ -128,4 +131,39 @@ public class SpecificationsUnitTests {
|
||||
assertThat(specification, is(notNullValue()));
|
||||
assertThat(specification.toPredicate(root, query, builder), is(predicate));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAJPA-523
|
||||
*/
|
||||
@Test
|
||||
public void specificationsShouldBeSerializable() {
|
||||
|
||||
Specifications<Object> specification = where(mockSpec);
|
||||
specification = specification.and(mockSpec);
|
||||
|
||||
assertThat(specification, is(notNullValue()));
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Specifications<Object> transferedSpecification = (Specifications<Object>) deserialize(serialize(specification));
|
||||
|
||||
assertThat(transferedSpecification, is(notNullValue()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAJPA-523
|
||||
*/
|
||||
@Test
|
||||
public void complexSpecificationsShouldBeSerializable() {
|
||||
|
||||
Specifications<Object> specification = where(mockSpec);
|
||||
specification = Specifications.not(specification.and(mockSpec).or(mockSpec));
|
||||
|
||||
assertThat(specification, is(notNullValue()));
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Specifications<Object> transferedSpecification = (Specifications<Object>) deserialize(serialize(specification));
|
||||
|
||||
assertThat(transferedSpecification, is(notNullValue()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user