1   /*
2    * Copyright 2006-2010 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package net.sourceforge.domian.specification;
17  
18  
19  import java.util.Arrays;
20  import java.util.Collections;
21  import java.util.Date;
22  import java.util.List;
23  
24  import org.apache.commons.lang.builder.EqualsBuilder;
25  import org.apache.commons.lang.builder.HashCodeBuilder;
26  
27  import net.sourceforge.domian.entity.AbstractUUIDEntity;
28  import static net.sourceforge.domian.specification.SpecificationFactory.is;
29  import static net.sourceforge.domian.specification.SpecificationFactory.isAfter;
30  import static net.sourceforge.domian.specification.SpecificationFactory.isBefore;
31  import static net.sourceforge.domian.specification.SpecificationFactory.isEqualTo;
32  import static net.sourceforge.domian.specification.SpecificationFactory.isGreaterThanOrEqualTo;
33  import net.sourceforge.domian.test.domain.Customer;
34  import static net.sourceforge.domian.test.domain.Customer.Gender;
35  import net.sourceforge.domian.test.domain.Order;
36  import net.sourceforge.domian.test.domain.OrderLine;
37  import net.sourceforge.domian.test.domain.Testdata;
38  import static net.sourceforge.domian.test.domain.Testdata.customer12;
39  import static net.sourceforge.domian.test.domain.Testdata.order23;
40  import static net.sourceforge.domian.test.domain.Testdata.thisDate;
41  import static net.sourceforge.domian.test.domain.Testdata.thisMonth;
42  import static net.sourceforge.domian.test.domain.Testdata.thisYear;
43  import static net.sourceforge.domian.test.domain.Testdata.twoDaysAgo;
44  import static net.sourceforge.domian.test.domain.Testdata.yesterday;
45  import static net.sourceforge.domian.util.DateUtils.getTime;
46  import net.sourceforge.domian.util.ReflectionUtils;
47  
48  import junit.framework.TestCase;
49  
50  
51  public class SpecificationsShouldBeImmutableTest extends TestCase {
52  
53      public void testPreventValueFromEscapingEncapsulation_MutableValueObject() {
54          final Boolean originalDoCopyObjectFlag = ReflectionUtils.DO_COPY_OBJECTS;
55          ReflectionUtils.DO_COPY_OBJECTS = true;
56  
57          Date thirtyYearsAgo = getTime(thisYear - 30, thisMonth, thisDate);
58          Date thirtyYearsAhead = getTime(thisYear + 30, thisMonth, thisDate);
59  
60          Specification<Date> lessThanThirtyYearsAgo = isAfter(thirtyYearsAgo);
61          Specification<Date> lessThanThirtyYearsAhead = isBefore(thirtyYearsAhead);
62  
63          Specification<Date> plusMinusThirtyYears = is(lessThanThirtyYearsAgo).and(lessThanThirtyYearsAhead);
64          assertTrue(plusMinusThirtyYears.isSatisfiedBy(new Date()));
65          assertTrue(plusMinusThirtyYears.isSatisfiedBy(getTime(1990, 7, 30)));
66          assertTrue(plusMinusThirtyYears.isSatisfiedBy(getTime(2037, 7, 29)));
67          assertFalse(plusMinusThirtyYears.isSatisfiedBy(getTime(1977, 7, 30)));
68          assertFalse(plusMinusThirtyYears.isSatisfiedBy(getTime(2050, 7, 30)));
69  
70          // Altering object previous given to value bound specification (should have been cloned)
71          thirtyYearsAgo.setTime(new Date().getTime());
72  
73          // Should still hold (when deep copying is activated)
74          assertFalse(plusMinusThirtyYears.isSatisfiedBy(null));
75          assertTrue(plusMinusThirtyYears.isSatisfiedBy(new Date()));
76          assertTrue(plusMinusThirtyYears.isSatisfiedBy(getTime(1990, 7, 30)));
77          assertTrue(plusMinusThirtyYears.isSatisfiedBy(getTime(2037, 7, 29)));
78          assertFalse(plusMinusThirtyYears.isSatisfiedBy(getTime(1977, 7, 30)));
79          assertFalse(plusMinusThirtyYears.isSatisfiedBy(getTime(2050, 7, 30)));
80  
81          // Altering object obtained via getValue() (should be same as original object)
82          thirtyYearsAgo = getTime(thisYear - 30, thisMonth, thisDate);
83          lessThanThirtyYearsAgo = isAfter(thirtyYearsAgo);
84          lessThanThirtyYearsAhead = isBefore(thirtyYearsAhead);
85          plusMinusThirtyYears = is(lessThanThirtyYearsAgo).and(lessThanThirtyYearsAhead);
86          // Obtaining value bound object
87          Date obtainedThirtyYearsAgo = ((ValueBoundSpecification<Date>) lessThanThirtyYearsAgo).getValue();
88          assertSame(obtainedThirtyYearsAgo, thirtyYearsAgo);
89          // Altering obtained object, which is the same as previously given to value bound specification
90          obtainedThirtyYearsAgo.setTime(new Date().getTime());
91          // Should still hold
92          assertFalse(plusMinusThirtyYears.isSatisfiedBy(null));
93          assertTrue(plusMinusThirtyYears.isSatisfiedBy(new Date()));
94          assertTrue(plusMinusThirtyYears.isSatisfiedBy(getTime(1990, 7, 30)));
95          assertTrue(plusMinusThirtyYears.isSatisfiedBy(getTime(2037, 7, 29)));
96          assertFalse(plusMinusThirtyYears.isSatisfiedBy(getTime(1977, 7, 30)));
97          assertFalse(plusMinusThirtyYears.isSatisfiedBy(getTime(2050, 7, 30)));
98  
99          // Deep copying deactivated
100         ReflectionUtils.DO_COPY_OBJECTS = false;
101         lessThanThirtyYearsAgo = isAfter(thirtyYearsAgo);
102         lessThanThirtyYearsAhead = isBefore(thirtyYearsAhead);
103         plusMinusThirtyYears = is(lessThanThirtyYearsAgo).and(lessThanThirtyYearsAhead);
104         // Altering object previous given to value bound specification (should have been cloned)
105         thirtyYearsAgo.setTime(new Date().getTime());
106         assertFalse(plusMinusThirtyYears.isSatisfiedBy(new Date()));
107         assertFalse(plusMinusThirtyYears.isSatisfiedBy(getTime(1990, 7, 30)));
108         assertTrue(plusMinusThirtyYears.isSatisfiedBy(getTime(2037, 7, 29)));
109 
110         // Reset
111         ReflectionUtils.DO_COPY_OBJECTS = originalDoCopyObjectFlag;
112     }
113 
114 
115     public void testPreventValueFromEscapingEncapsulation_BusinessKeyEntityObject() {
116         Customer customer12 = new Customer(12L, twoDaysAgo).gender(Gender.MALE).birthDate(getTime(1978, 11, 25));
117         CustomerAwareBusinessKeyOrder order22 = new CustomerAwareBusinessKeyOrder(22L, yesterday, customer12, Collections.<OrderLine>emptyList());
118         Specification<Order> order22Spec = isEqualTo((Order) order22);
119 
120         assertTrue(order22Spec.isSatisfiedBy(order22));
121         assertFalse(order22Spec.isSatisfiedBy(order23));
122 
123         /* Altering object previous given to value bound specification.
124            Should have been cloned because it does not extends AbstractUUIDEntity (if object copying is activated) */
125         customer12.setCustomerId(42L);
126         // Should still hold (at all times, if object copying is not activated, the specification is altered together with the value)
127         assertTrue(order22Spec.isSatisfiedBy(order22));
128         assertFalse(order22Spec.isSatisfiedBy(order23));
129 
130         // Altering object obtained via getValue() (should be same as original object)
131         customer12 = new Customer(12L, twoDaysAgo).gender(Gender.MALE).birthDate(getTime(1978, 11, 25));
132         order22 = new CustomerAwareBusinessKeyOrder(22L, yesterday, customer12, Collections.<OrderLine>emptyList());
133         order22Spec = isEqualTo((Order) order22);
134         // Obtaining value bound object
135         Customer obtainedCustomer12 = ((ValueBoundSpecification<Order>) order22Spec).getValue().getCustomer();
136         assertSame(obtainedCustomer12, customer12);
137         // Altering object previously given to value bound specification
138         obtainedCustomer12.setCustomerId(42L);
139         // Should still hold
140         assertTrue(order22Spec.isSatisfiedBy(order22));
141         assertFalse(order22Spec.isSatisfiedBy(order23));
142     }
143 
144 
145     public void testPreventValueFromEscapingEncapsulation_ComparableEntityObject() {
146         final Boolean originalDoCopyObjectFlag = ReflectionUtils.DO_COPY_OBJECTS;
147         ReflectionUtils.DO_COPY_OBJECTS = true;
148 
149         ComparableEntity entityWithInt1024 = new ComparableEntity();
150         entityWithInt1024.field1 = 1024;
151         Specification<ComparableEntity> greaterThanEntityWithInt1024 = isGreaterThanOrEqualTo(entityWithInt1024);
152 
153         ComparableEntity entityWithInt1025 = new ComparableEntity();
154         entityWithInt1025.field1 = 1025;
155         ComparableEntity entityWithInt1023 = new ComparableEntity();
156         entityWithInt1023.field1 = 1023;
157 
158         assertTrue(greaterThanEntityWithInt1024.isSatisfiedBy(entityWithInt1025));
159         assertFalse(greaterThanEntityWithInt1024.isSatisfiedBy(entityWithInt1023));
160 
161         // Altering object previous given to value bound specification (should have been cloned because it implements Comparable)
162         entityWithInt1024.field1 = 12;
163 
164         // Should still hold (if object copying is activated)
165         assertTrue(greaterThanEntityWithInt1024.isSatisfiedBy(entityWithInt1025));
166 
167         ReflectionUtils.DO_COPY_OBJECTS = false;
168         entityWithInt1024 = new ComparableEntity();
169         entityWithInt1024.field1 = 1024;
170         greaterThanEntityWithInt1024 = isGreaterThanOrEqualTo(entityWithInt1024);
171         assertTrue(greaterThanEntityWithInt1024.isSatisfiedBy(entityWithInt1025));
172         assertFalse(greaterThanEntityWithInt1024.isSatisfiedBy(entityWithInt1023));
173 
174         // Altering object previous given to value bound specification (should have been cloned because it implements Comparable)
175         entityWithInt1024.field1 = 12;
176 
177         // Should still hold (but don't since object copying is deactivated)
178         assertTrue(greaterThanEntityWithInt1024.isSatisfiedBy(entityWithInt1025));
179         assertTrue(greaterThanEntityWithInt1024.isSatisfiedBy(entityWithInt1023));
180 
181         // Reset
182         ReflectionUtils.DO_COPY_OBJECTS = originalDoCopyObjectFlag;
183     }
184 
185 
186     public void testPreventValueFromEscapingEncapsulation_EntityObject() {
187         // Similar to Fixtures.order22
188         Order order22 = new Order(22L, yesterday, customer12, Collections.<OrderLine>emptyList());
189 
190         Specification<Order> order22Spec = isEqualTo(order22);
191 
192         assertFalse(order22Spec.isSatisfiedBy(Testdata.order22));
193         assertFalse(order22Spec.isSatisfiedBy(Testdata.order23));
194 
195         // Altering object previous given to value bound specification (not cloned because it extends AbstractUUIDEntity, with its immutable hashCode/equals methods)
196         order22.setOrderId(23L);
197         // Should still hold
198         assertFalse(order22Spec.isSatisfiedBy(Testdata.order22));
199         assertFalse(order22Spec.isSatisfiedBy(Testdata.order23));
200 
201         // Altering object obtained via getValue() (should be same as original object)
202         order22 = new Order(22L, yesterday, customer12, Collections.<OrderLine>emptyList());
203         order22Spec = isEqualTo(order22);
204         // Obtaining value bound object
205         Order obtainedOrder22 = ((ValueBoundSpecification<Order>) order22Spec).getValue();
206         assertSame(obtainedOrder22, order22);
207         // Altering object previously given to value bound specification
208         obtainedOrder22.setOrderId(23L);
209         // Should still hold
210         assertFalse(order22Spec.isSatisfiedBy(Testdata.order22));
211         assertFalse(order22Spec.isSatisfiedBy(Testdata.order23));
212     }
213 }
214 
215 
216 /** Custom test class kind of not belonging to the test domain. */
217 class CustomerAwareBusinessKeyOrder extends Order {
218 
219     CustomerAwareBusinessKeyOrder(final Long orderId, final Date orderDate, final Customer customer, final List<OrderLine> orderLines) {
220         super(orderId, orderDate, customer, orderLines);
221     }
222 
223     public List<?> getKey() {
224         return Collections.unmodifiableList(Arrays.asList(orderId, customer));
225     }
226 
227     @Override
228     public int hashCode() {
229         final HashCodeBuilder hashCodeBuilder = new HashCodeBuilder(9373645, 28373649);
230         for (Object partialKey : getKey()) {
231             if (partialKey != null) {
232                 hashCodeBuilder.append(partialKey);
233             } else {
234                 return System.identityHashCode(this);
235             }
236         }
237         return hashCodeBuilder.toHashCode();
238     }
239 
240     @Override
241     public boolean equals(final Object otherObject) {
242         if (otherObject == null) {
243             return false;
244         }
245         if (!(otherObject instanceof Order)) {
246             return false;
247         }
248         Order otherOrder = (Order) otherObject;
249         return new EqualsBuilder()
250                 .append(getOrderId(), otherOrder.getOrderId())
251                 .append(getCustomer(), otherOrder.getCustomer())
252                 .isEquals();
253     }
254 }
255 
256 
257 class ComparableEntity extends AbstractUUIDEntity implements Comparable<ComparableEntity> {
258     Integer field1;
259 
260     public int compareTo(ComparableEntity otherComparableEntityo) {
261         return this.field1 - otherComparableEntityo.field1;
262     }
263 }