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.Date;
20  import java.util.HashSet;
21  import java.util.Set;
22  
23  import org.junit.Test;
24  
25  import net.sourceforge.domian.test.domain.Customer;
26  import net.sourceforge.domian.test.domain.Order;
27  import net.sourceforge.domian.test.domain.OrderLine;
28  import net.sourceforge.domian.test.domain.VipCustomer;
29  
30  import static net.sourceforge.domian.specification.ParameterizedSpecification.createParameterizedSpecification;
31  import static net.sourceforge.domian.specification.SpecificationFactory.a;
32  import static net.sourceforge.domian.specification.SpecificationFactory.all;
33  import static net.sourceforge.domian.specification.SpecificationFactory.allInstancesOfType;
34  import static net.sourceforge.domian.specification.SpecificationFactory.an;
35  import static net.sourceforge.domian.specification.SpecificationFactory.createSpecificationFor;
36  import static net.sourceforge.domian.specification.SpecificationFactory.eitherOf;
37  import static net.sourceforge.domian.specification.SpecificationFactory.empty;
38  import static net.sourceforge.domian.specification.SpecificationFactory.equalTo;
39  import static net.sourceforge.domian.specification.SpecificationFactory.in;
40  import static net.sourceforge.domian.specification.SpecificationFactory.is;
41  import static net.sourceforge.domian.specification.SpecificationFactory.isA;
42  import static net.sourceforge.domian.specification.SpecificationFactory.isAfterOrAtTheSameTimeAs;
43  import static net.sourceforge.domian.specification.SpecificationFactory.isBefore;
44  import static net.sourceforge.domian.specification.SpecificationFactory.isEither;
45  import static net.sourceforge.domian.specification.SpecificationFactory.isEitherOf;
46  import static net.sourceforge.domian.specification.SpecificationFactory.isEqualTo;
47  import static net.sourceforge.domian.specification.SpecificationFactory.isFalse;
48  import static net.sourceforge.domian.specification.SpecificationFactory.isOneOf;
49  import static net.sourceforge.domian.specification.SpecificationFactory.isTrue;
50  import static net.sourceforge.domian.specification.SpecificationFactory.not;
51  import static net.sourceforge.domian.test.domain.Testdata.femaleCustomer;
52  import static net.sourceforge.domian.test.domain.Testdata.maleCustomer;
53  import static net.sourceforge.domian.test.domain.Testdata.twoDaysAgo;
54  import static net.sourceforge.domian.test.domain.Testdata.yesterday;
55  import static net.sourceforge.domian.util.DateUtils.getTime;
56  import static org.junit.Assert.assertEquals;
57  import static org.junit.Assert.assertFalse;
58  import static org.junit.Assert.assertNotSame;
59  import static org.junit.Assert.assertTrue;
60  import static org.junit.Assert.fail;
61  
62  
63  public class ParameterizedSpecificationTest {
64  
65      Customer customer = maleCustomer;
66      String fieldName = "orderLineId";
67      Specification<Long> long42 = isEqualTo(42L);
68  
69  
70      @Test
71      public void shouldNotAcceptNullAsDeclaringClass() {
72          try {
73              createParameterizedSpecification(null, null, null);
74              fail("Should have thrown exception");
75  
76          } catch (IllegalArgumentException e) {
77              String expectedMessage = "Declaring class parameter cannot be null";
78              assertEquals(expectedMessage, e.getMessage());
79          }
80      }
81  
82  
83      @Test
84      public void shouldNotAcceptNullAsAccessibleObjectName() {
85          try {
86              createParameterizedSpecification(Customer.class, null, long42);
87              fail("Should have thrown exception");
88  
89          } catch (IllegalArgumentException e) {
90              String expectedMessage = "Accessible object name parameter cannot be null";
91              assertEquals(expectedMessage, e.getMessage());
92          }
93      }
94  
95  
96      @Test
97      public void shouldNotAcceptNullAsAccessibleObjectSpecification() {
98          try {
99              createParameterizedSpecification(Customer.class, fieldName, null);
100             fail("Should have thrown exception");
101 
102         } catch (IllegalArgumentException e) {
103             String expectedMessage = "Accessible object specification parameter cannot be null";
104             assertEquals(expectedMessage, e.getMessage());
105         }
106     }
107 
108 
109     @Test
110     public void shouldNotAcceptIllegalAccessibleObjectName() {
111         try {
112             createParameterizedSpecification(Customer.class, "1_illegalAccessibleObjectName", isEqualTo(106L));
113             fail("Should have thrown exception");
114 
115         } catch (IllegalArgumentException e) {
116             String expectedMessage = "Accessible object name parameter \"1_illegalAccessibleObjectName\" is neither a valid method name, nor a valid field name";
117             assertEquals(expectedMessage, e.getMessage());
118         }
119         try {
120             createParameterizedSpecification(Customer.class, ".illegalAccessibleObjectName", isEqualTo(106L));
121             fail("Should have thrown exception");
122 
123         } catch (IllegalArgumentException e) {
124             String expectedMessage = "Accessible object name parameter \".illegalAccessibleObjectName\" is neither a valid method name, nor a valid field name";
125             assertEquals(expectedMessage, e.getMessage());
126         }
127     }
128 
129 
130     @Test
131     public void shouldNotAcceptNonExistingAccessibleObjectName() {
132         try {
133             createParameterizedSpecification(Customer.class, "nonExistingAccessibleObjectName", isEqualTo(106L));
134             fail("Should have thrown exception");
135 
136         } catch (IllegalArgumentException e) {
137             String expectedMessage = "Neither a field nor a method (with no formal parameters) named \"nonExistingAccessibleObjectName\" found in class net.sourceforge.domian.test.domain.Customer";
138             assertEquals(expectedMessage, e.getMessage());
139         }
140     }
141 
142 
143     @Test
144     public void shouldAcceptAccessibleObjectNameFoundInSuperclass() {
145         createParameterizedSpecification(VipCustomer.class, "customerId", isEqualTo(106L));
146     }
147 
148 
149     @Test
150     public void shouldAcceptParameterizedSpecificationCandidates() {
151         Specification<Long> number42 = is(42L);
152         Specification<Long> number44 = is(44L);
153         Specification<Date> isBeforeYesterday = isBefore(yesterday);
154         Specification<Customer> compositeSpec = createSpecificationFor(Customer.class).where("customerId", isEitherOf(number42, number44)).and("membershipDate", isBeforeYesterday);
155         ParameterizedSpecification<Customer, ? extends Number> simpleParameterizedSpec = createParameterizedSpecification(Customer.class, "customerId", is(number42));
156         ParameterizedSpecification<Customer, ? extends Number> parameterizedSpec = createParameterizedSpecification(Customer.class, "customerId", isEitherOf(number42, number44));
157 
158         Specification parameterizedSpecificationsWithDisjunctionFieldSpec = all(ParameterizedSpecification.class).where("accessibleObjectSpecification", isA(DisjunctionSpecification.class));
159 
160         assertFalse(parameterizedSpecificationsWithDisjunctionFieldSpec.isSatisfiedBy(femaleCustomer));
161         assertFalse(parameterizedSpecificationsWithDisjunctionFieldSpec.isSatisfiedBy(number42));
162         assertFalse(parameterizedSpecificationsWithDisjunctionFieldSpec.isSatisfiedBy(new CollectionSpecification(CollectionSpecification.CollectionSpecificationScope.SIZE, new EqualSpecification(1))));
163         assertFalse(parameterizedSpecificationsWithDisjunctionFieldSpec.isSatisfiedBy(compositeSpec));
164         assertFalse(parameterizedSpecificationsWithDisjunctionFieldSpec.isSatisfiedBy(simpleParameterizedSpec));
165         assertTrue(parameterizedSpecificationsWithDisjunctionFieldSpec.isSatisfiedBy(parameterizedSpec));
166     }
167 
168 
169     @Test
170     public void testGetType() {
171         Specification spec = createParameterizedSpecification(OrderLine.class, fieldName, long42);
172         assertEquals(OrderLine.class, spec.getType());
173     }
174 
175 
176     @Test
177     public void shouldNotAcceptMethodsWithDeclaredParameters() {
178         try {
179             all(Order.class).where("addOrderLine", isTrue());
180             fail("Should have thrown exception");
181 
182         } catch (IllegalArgumentException e) {
183             String expectedMessage = "Neither a field nor a method (with no formal parameters) named \"addOrderLine\" found in class net.sourceforge.domian.test.domain.Order";
184             assertEquals(expectedMessage, e.getMessage());
185         }
186     }
187 
188 
189     @Test
190     public void shouldNotApproveOfWhereClauseFromLeafSpecification() {
191         try {
192             createParameterizedSpecification(OrderLine.class, fieldName, long42).where("value", is(not(equalTo(12))));
193 
194         } catch (UnsupportedOperationException e) {
195             String expectedMessage = "'where' clause is applicable only for CompositeSpecification instances";
196             assertEquals(expectedMessage, e.getMessage());
197         }
198     }
199 
200 
201     @Test
202     public void testMethodInvocation() {
203         /* "pendingOrderLine" -> pendingOrderLine() | isPendingOrderLine() | getPendingOrderLine()
204          * "isPendingOrderLine" -> isPendingOrderLine() | pendingOrderLine() | getPendingOrderLine()
205          */
206         OrderLine pendingOrderLine = new OrderLine(122L);
207         OrderLine paidForOrderLine = new OrderLine(123L).paymentReceived(true);
208 
209         Specification<OrderLine> pendingOrderLineSpec = an(OrderLine.class).where("paymentReceived", isFalse());
210         Specification<OrderLine> paidForOrderLineSpec = an(OrderLine.class).where("paymentReceived", isTrue());
211 
212         // Parameterized through field
213         assertTrue(paidForOrderLineSpec.isSatisfiedBy(paidForOrderLine));
214         assertFalse(paidForOrderLineSpec.isSatisfiedBy(pendingOrderLine));
215         assertFalse(pendingOrderLineSpec.isSatisfiedBy(paidForOrderLine));
216         assertTrue(pendingOrderLineSpec.isSatisfiedBy(pendingOrderLine));
217 
218         // Parameterized through method
219         pendingOrderLineSpec = an(OrderLine.class).where("pending", isTrue());
220         paidForOrderLineSpec = an(OrderLine.class).where("isPending", isFalse());
221 
222         assertTrue(paidForOrderLineSpec.isSatisfiedBy(paidForOrderLine));
223         assertFalse(paidForOrderLineSpec.isSatisfiedBy(pendingOrderLine));
224         assertFalse(pendingOrderLineSpec.isSatisfiedBy(paidForOrderLine));
225         assertTrue(pendingOrderLineSpec.isSatisfiedBy(pendingOrderLine));
226 
227         // Parameterized through package private method
228         pendingOrderLineSpec = an(OrderLine.class).where("privateIsPending", isTrue());
229 
230         assertFalse(pendingOrderLineSpec.isSatisfiedBy(paidForOrderLine));
231         assertTrue(pendingOrderLineSpec.isSatisfiedBy(pendingOrderLine));
232     }
233 
234 
235     @Test
236     public void testMethodFromSuperclassInvocation() {
237         VipCustomer customer1 = (VipCustomer) new VipCustomer(1L, getTime(2006, 12, 1)).name("Brandon");
238         VipCustomer customer2 = (VipCustomer) new VipCustomer(2L).name("Brandon").membershipDate(getTime(2007, 2, 1));
239 
240         Specification<VipCustomer> spec = a(VipCustomer.class).where("name", is("Brandon")).and("hasAlwaysBeenVipCustomer", isTrue());
241 
242         assertTrue(spec.isSatisfiedBy(customer1));
243         assertFalse(spec.isSatisfiedBy(customer2));
244     }
245 
246 
247     @Test
248     public void testValueDisjunction() {
249         Customer customer = new Customer(42L, new Date());
250         Specification<Long> number42 = is(42L);
251         Specification<Customer> spec1 = createSpecificationFor(Customer.class).where("customerId", isEitherOf(number42));
252         Specification<Customer> spec2 = createSpecificationFor(Customer.class).where("customerId", isEither(equalTo(42L), equalTo(44L), equalTo(46L)));
253         try {
254             Specification spec3 = createSpecificationFor(Customer.class).where("customerId", isOneOf(1, 2, 3, 42));
255             fail("Should have thrown exception");
256 
257         } catch (IllegalArgumentException e) {
258             String expectedMessage = "Field \"customerId\" of type java.lang.Long [in class net.sourceforge.domian.test.domain.Customer], cannot be specified by a Specification<java.lang.Integer>";
259             assertEquals(expectedMessage, e.getMessage());
260         }
261         Specification<Customer> spec4 = all(Customer.class).where("customerId", in(1L, 2L, 3L, 4L));
262         Specification<Customer> spec5 = all(Customer.class).where("customerId", not(in(1L, 2L, 3L, 42L)));
263         Specification<Customer> spec6 = all(Customer.class).where("customerId", is(not(eitherOf(1L, 2L, 3L, 4L))));
264 
265         assertTrue(spec1.isSatisfiedBy(customer));
266         assertTrue(spec2.isSatisfiedBy(customer));
267         assertFalse(spec4.isSatisfiedBy(customer));
268         assertFalse(spec5.isSatisfiedBy(customer));
269         assertTrue(spec6.isSatisfiedBy(customer));
270     }
271 
272 
273     @Test
274     public void testHashCode() {
275         Specification spec1 = createParameterizedSpecification(OrderLine.class, fieldName, long42);
276         Specification spec2 = createParameterizedSpecification(OrderLine.class, fieldName, long42);
277         Set<Specification> set = new HashSet<Specification>(2);
278         set.add(spec1);
279         set.add(spec2);
280         assertEquals(1, set.size());
281     }
282 
283 
284     @Test
285     public void testEquality() {
286         Specification spec1 = createParameterizedSpecification(OrderLine.class, fieldName, long42);
287         Specification spec2 = null;
288         assertNotSame(spec1, spec2);
289         assertFalse(spec1.equals(spec2));
290 
291         spec1 = createParameterizedSpecification(OrderLine.class, fieldName, long42);
292         spec2 = new NotNullSpecification(Order.class);
293         assertNotSame(spec1, spec2);
294         assertFalse(spec1.equals(spec2));
295 
296         spec1 = createParameterizedSpecification(OrderLine.class, fieldName, long42);
297         spec2 = createParameterizedSpecification(OrderLine.class, fieldName, long42);
298         assertNotSame(spec1, spec2);
299         assertEquals(spec1, spec2);
300 
301         /* Not accurate - the bug is in subsumptioning logic :-\ */
302         Specification customerOrdersIsNotEmpty = createParameterizedSpecification(Customer.class, "orders", is(not(empty())));
303         Specification vipCustomerOrdersIsNotEmpty = createParameterizedSpecification(VipCustomer.class, "orders", is(not(empty())));
304 
305         assertNotSame(vipCustomerOrdersIsNotEmpty, customerOrdersIsNotEmpty);
306         assertEquals(vipCustomerOrdersIsNotEmpty, customerOrdersIsNotEmpty);
307 
308         assertNotSame(customerOrdersIsNotEmpty, vipCustomerOrdersIsNotEmpty);
309         assertEquals(customerOrdersIsNotEmpty, vipCustomerOrdersIsNotEmpty);
310     }
311 
312 
313     protected final CompositeSpecification<Customer> customers = allInstancesOfType(Customer.class);
314     protected final CompositeSpecification<Customer> pioneerCustomers = customers.where("membershipDate", isBefore(twoDaysAgo));
315     protected final CompositeSpecification<Customer> newCustomers = customers.where("membershipDate", isAfterOrAtTheSameTimeAs(twoDaysAgo));
316 
317     @Test
318     public void testNonEquality() {
319         assertFalse(newCustomers.equals(pioneerCustomers));
320         assertFalse(pioneerCustomers.equals(newCustomers));
321     }
322 }