View Javadoc

1   /**
2    * Copyright 2005-2011 The Kuali Foundation
3    *
4    * Licensed under the Educational Community 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.opensource.org/licenses/ecl2.php
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 org.kuali.rice.kew.docsearch.xml;
17  
18  import org.junit.Test;
19  import org.kuali.rice.core.framework.persistence.jdbc.sql.SQLUtils;
20  import org.kuali.rice.kew.api.WorkflowDocument;
21  import org.kuali.rice.kew.api.WorkflowDocumentFactory;
22  import org.kuali.rice.kew.api.document.attribute.WorkflowAttributeDefinition;
23  import org.kuali.rice.kew.api.document.search.DocumentSearchCriteria;
24  import org.kuali.rice.kew.api.document.search.DocumentSearchResults;
25  import org.kuali.rice.kew.docsearch.DocumentSearchInternalUtils;
26  import org.kuali.rice.kew.docsearch.DocumentSearchTestBase;
27  import org.kuali.rice.kew.docsearch.TestXMLSearchableAttributeDateTime;
28  import org.kuali.rice.kew.docsearch.TestXMLSearchableAttributeFloat;
29  import org.kuali.rice.kew.docsearch.TestXMLSearchableAttributeLong;
30  import org.kuali.rice.kew.docsearch.TestXMLSearchableAttributeString;
31  import org.kuali.rice.kew.docsearch.service.DocumentSearchService;
32  import org.kuali.rice.kew.doctype.bo.DocumentType;
33  import org.kuali.rice.kew.doctype.service.DocumentTypeService;
34  import org.kuali.rice.kew.exception.WorkflowServiceErrorException;
35  import org.kuali.rice.kew.service.KEWServiceLocator;
36  import org.kuali.rice.kim.api.identity.Person;
37  import org.kuali.rice.kim.api.services.KimApiServiceLocator;
38  
39  import java.math.BigDecimal;
40  import java.util.Calendar;
41  
42  import static org.junit.Assert.*;
43  
44  /**
45   * Tests the StandardGenericXMLSearchableAttribute.
46   *
47   * KULWF-654: Tests the resolution to this issue by configuring a CustomActionListAttribute as well as a
48   * searchable attribute.
49   */
50  public class StandardGenericXMLSearchableAttributeRangesTest extends DocumentSearchTestBase {
51  
52      protected void loadTestData() throws Exception {
53          loadXmlFile("XmlConfig.xml");
54      }
55  
56      /*
57       * Test method for 'org.kuali.rice.kew.docsearch.xml.StandardGenericXMLSearchableAttribute.getSearchingRows()'
58       */
59      /*
60      @Ignore("See KULRICE-2988")
61      @Test public void testGetSearchingRowsUsingRangeSearches() {
62          StandardGenericXMLSearchableAttribute searchAttribute = getAttribute("XMLSearchableAttributeStringRange");
63          String documentTypeName = "SearchDocType";
64          DocumentSearchContext context = DocSearchUtils.getDocumentSearchContext("", documentTypeName, "");
65          List<Row> searchRows = searchAttribute.getSearchingRows(context);
66          if ((new SearchableAttributeStringValue()).allowsRangeSearches()) {
67          	fail("Cannot perform range search on string field at database level");
68          } else {
69              assertEquals("Invalid number of search rows", 1, searchRows.size());
70              Row row = searchRows.get(0);
71              assertEquals("Invalid number of fields for search row", 1, row.getFields().size());
72              assertFalse("Field is the member of a range when ranges are not allowed",((Field)row.getField(0)).isMemberOfRange());
73          }
74  
75          searchAttribute = getAttribute("XMLSearchableAttributeStdLongRange");
76          // search def :  rangeSearch=true
77          // range def  :
78          // upper def  :
79          // lower def  :
80          searchRows = searchAttribute.getSearchingRows(context);
81          if ((new SearchableAttributeLongValue()).allowsRangeSearches()) {
82              assertEquals("Invalid number of search rows", 2, searchRows.size());
83              for (int i = 1; i <= searchRows.size(); i++) {
84                  Row row = searchRows.get(i - 1);
85  	            assertEquals("Invalid number of fields for search row " + i, 1, row.getFields().size());
86  	            Field field = (Field)(row.getField(0));
87  	            assertTrue("Field should be the member of a range",field.isMemberOfRange());
88  	            assertTrue("Field should not be inclusive",field.isInclusive());
89  	            assertFalse("Field should not be using datepicker", field.isDatePicker());
90  			}
91          } else {
92              assertEquals("Invalid number of search rows", 1, searchRows.size());
93              Row row = searchRows.get(0);
94              assertEquals("Invalid number of fields for search row", 1, row.getFields().size());
95              Field field = (Field)(row.getField(0));
96              assertFalse("Field is the member of a range when ranges are not allowed",field.isMemberOfRange());
97              assertFalse("Field is inclusive when ranges are not allowed",field.isInclusive());
98              assertFalse("Field should not be using datepicker", field.isDatePicker());
99          }
100 
101         searchAttribute = getAttribute("XMLSearchableAttributeStdFloatRange");
102         // search def :
103         // range def  :  inclusive=false
104         // upper def  :  label=ending
105         // lower def  :  label=starting
106         searchRows = searchAttribute.getSearchingRows(context);
107         if ((new SearchableAttributeFloatValue()).allowsRangeSearches()) {
108             assertEquals("Invalid number of search rows", 2, searchRows.size());
109             for (int i = 1; i <= searchRows.size(); i++) {
110                 Row row = searchRows.get(i - 1);
111 	            assertEquals("Invalid number of fields for search row " + i, 1, row.getFields().size());
112 	            Field field = (Field)(row.getField(0));
113 	            assertTrue("Upper and Lower Fields should be members of a range",field.isMemberOfRange());
114 	            assertFalse("Upper and Lower Fields should not be inclusive",field.isInclusive());
115 	            String labelValue = null;
116 	            if (field.getPropertyName().startsWith(KewApiConstants.SearchableAttributeConstants.RANGE_LOWER_BOUND_PROPERTY_PREFIX)) {
117 	            	labelValue = "starting";
118 	            } else if (field.getPropertyName().startsWith(KewApiConstants.SearchableAttributeConstants.RANGE_UPPER_BOUND_PROPERTY_PREFIX)) {
119 	            	labelValue = "ending";
120 	            } else {
121 	            	fail("Field should have prefix consistent with upper or lower bound of a range");
122 	            }
123 	            assertEquals("Field label is incorrect.", labelValue, field.getFieldLabel());
124 	            assertFalse("Field should not be using datepicker", field.isDatePicker());
125 			}
126         } else {
127             assertEquals("Invalid number of search rows", 1, searchRows.size());
128             Row row = searchRows.get(0);
129             assertEquals("Invalid number of fields for search row", 1, row.getFields().size());
130             Field field = (Field)(row.getField(0));
131             assertFalse("Field is the member of a range when ranges are not allowed",field.isMemberOfRange());
132             assertFalse("Field should not be using datepicker", field.isDatePicker());
133         }
134 
135         searchAttribute = getAttribute("XMLSearchableAttributeStdDateTimeRange");
136         // search def :  datePicker=false
137         // range def  :  inclusive=false
138         // upper def  :  inclusvie=true - datePicker=true
139         // lower def  :
140         searchRows = searchAttribute.getSearchingRows(context);
141         if ((new SearchableAttributeDateTimeValue()).allowsRangeSearches()) {
142             assertEquals("Invalid number of search rows", 2, searchRows.size());
143             for (int i = 0; i < searchRows.size(); i++) {
144                 Row row = searchRows.get(i);
145 	            assertTrue("Invalid number of fields for search row", row.getFields().size() > 0);
146 	            Field field = (Field)(row.getField(0));
147 	            assertTrue("Field should be the member of a range search", field.isMemberOfRange());
148 	            if (field.getPropertyName().startsWith(KewApiConstants.SearchableAttributeConstants.RANGE_LOWER_BOUND_PROPERTY_PREFIX)) {
149 	            	// this is the lower bound row
150 	            	assertFalse("Upper Field should not be using datepicker field", field.isDatePicker());
151 	            	assertFalse("Upper Field should not be inclusive", field.isInclusive());
152 	            } else if (field.getPropertyName().startsWith(KewApiConstants.SearchableAttributeConstants.RANGE_UPPER_BOUND_PROPERTY_PREFIX)) {
153 	            	// this is the upper bound row
154 	            	assertTrue("Upper Field should be using datepicker field", field.isDatePicker());
155 	            	assertTrue("Upper Field should not be inclusive", field.isInclusive());
156 	            	assertEquals("Row should have two fields (including the datepicker field)", 2, row.getFields().size());
157 	            	assertEquals("Second field in row  should be of type datepicker", Field.DATEPICKER, row.getField(1).getFieldType());
158 	            } else {
159 	            	fail("Field should have prefix consistent with upper or lower bound of a range");
160 	            }
161 			}
162         } else {
163             assertEquals("Invalid number of search rows", 1, searchRows.size());
164             Row row = searchRows.get(0);
165             // check to make sure our datepicker field didn't make it to the search rows
166             assertEquals("Invalid number of fields", 1, row.getFields().size());
167             assertFalse("Field is the member of a range when ranges are not allowed",((Field)(row.getField(0))).isMemberOfRange());
168         }
169     }
170 
171     */
172     /*
173      * Test method for 'org.kuali.rice.kew.docsearch.xml.StandardGenericXMLSearchableAttribute.validateUserSearchInputs(Map)'
174      */
175     /*
176     @Test  public void testValidateUserSearchRangeInputs() {
177     	// upper bound and lower bound fields should be using same validation... we just altername which formKey we use here
178         StandardGenericXMLSearchableAttribute searchAttribute = getAttribute("XMLSearchableAttributeStringRange");
179         Map<Object, Object> paramMap = new HashMap<Object, Object>();
180         String documentTypeName = "SearchDocType";
181         DocumentSearchContext context = DocSearchUtils.getDocumentSearchContext("", documentTypeName, "");
182         paramMap.put(KewApiConstants.SearchableAttributeConstants.RANGE_LOWER_BOUND_PROPERTY_PREFIX + TestXMLSearchableAttributeString.SEARCH_STORAGE_KEY, "jack");
183 
184         List<WorkflowAttributeValidationError> validationErrors = searchAttribute.validateUserSearchInputs(paramMap, context);
185         assertEquals("Validation should not have returned an error.", 0, validationErrors.size());
186         paramMap.clear();
187         paramMap.put(KewApiConstants.SearchableAttributeConstants.RANGE_UPPER_BOUND_PROPERTY_PREFIX + TestXMLSearchableAttributeString.SEARCH_STORAGE_KEY, "jack.jack");
188         validationErrors = searchAttribute.validateUserSearchInputs(paramMap, context);
189         assertEquals("Validation should return a single error message.", 1, validationErrors.size());
190         WorkflowAttributeValidationError error = validationErrors.get(0);
191         assertEquals("Validation error should match xml attribute message", "Invalid first name", error.getMessage());
192         paramMap.clear();
193         paramMap.put(KewApiConstants.SearchableAttributeConstants.RANGE_LOWER_BOUND_PROPERTY_PREFIX + TestXMLSearchableAttributeString.SEARCH_STORAGE_KEY, "jack*jack");
194         validationErrors = searchAttribute.validateUserSearchInputs(paramMap, context);
195         assertEquals("Validation should return a single error message.", 0, validationErrors.size());
196 
197         searchAttribute = getAttribute("XMLSearchableAttributeStdLongRange");
198         paramMap = new HashMap<Object, Object>();
199         paramMap.put(KewApiConstants.SearchableAttributeConstants.RANGE_UPPER_BOUND_PROPERTY_PREFIX + TestXMLSearchableAttributeLong.SEARCH_STORAGE_KEY, TestXMLSearchableAttributeLong.SEARCH_STORAGE_VALUE.toString());
200         validationErrors = searchAttribute.validateUserSearchInputs(paramMap, context);
201         assertEquals("Validation should not have returned an error.", 0, validationErrors.size());
202         paramMap.clear();
203         paramMap.put(KewApiConstants.SearchableAttributeConstants.RANGE_LOWER_BOUND_PROPERTY_PREFIX + TestXMLSearchableAttributeLong.SEARCH_STORAGE_KEY, TestXMLSearchableAttributeLong.SEARCH_STORAGE_VALUE.toString() + ".33");
204         validationErrors = searchAttribute.validateUserSearchInputs(paramMap, context);
205         assertEquals("Validation should return a single error message.", 1, validationErrors.size());
206         error = validationErrors.get(0);
207         assertTrue("Validation error is incorrect", error.getMessage().endsWith("does not conform to standard validation for field type."));
208         paramMap.clear();
209         paramMap.put(KewApiConstants.SearchableAttributeConstants.RANGE_UPPER_BOUND_PROPERTY_PREFIX + TestXMLSearchableAttributeLong.SEARCH_STORAGE_KEY, "jack*jack");
210         validationErrors = searchAttribute.validateUserSearchInputs(paramMap, context);
211         assertEquals("Validation should return a single error message.", 1, validationErrors.size());
212         error = validationErrors.get(0);
213         assertTrue("Validation error is incorrect", error.getMessage().endsWith("does not conform to standard validation for field type."));
214 
215         searchAttribute = getAttribute("XMLSearchableAttributeStdFloatRange");
216         paramMap = new HashMap<Object, Object>();
217         paramMap.put(KewApiConstants.SearchableAttributeConstants.RANGE_LOWER_BOUND_PROPERTY_PREFIX + TestXMLSearchableAttributeFloat.SEARCH_STORAGE_KEY, TestXMLSearchableAttributeFloat.SEARCH_STORAGE_VALUE.toString());
218         validationErrors = searchAttribute.validateUserSearchInputs(paramMap, context);
219         assertEquals("Validation should not have returned an error.", 0, validationErrors.size());
220         paramMap.clear();
221         paramMap.put(KewApiConstants.SearchableAttributeConstants.RANGE_UPPER_BOUND_PROPERTY_PREFIX + TestXMLSearchableAttributeFloat.SEARCH_STORAGE_KEY, TestXMLSearchableAttributeFloat.SEARCH_STORAGE_VALUE.toString() + "a");
222         validationErrors = searchAttribute.validateUserSearchInputs(paramMap, context);
223         assertEquals("Validation should return a single error message.", 1, validationErrors.size());
224         error = validationErrors.get(0);
225         assertTrue("Validation error is incorrect", error.getMessage().endsWith("does not conform to standard validation for field type."));
226         paramMap.clear();
227         paramMap.put(KewApiConstants.SearchableAttributeConstants.RANGE_LOWER_BOUND_PROPERTY_PREFIX + TestXMLSearchableAttributeFloat.SEARCH_STORAGE_KEY, TestXMLSearchableAttributeFloat.SEARCH_STORAGE_VALUE.toString() + "*");
228         validationErrors = searchAttribute.validateUserSearchInputs(paramMap, context);
229         assertEquals("Validation should return a single error message.", 1, validationErrors.size());
230         error = validationErrors.get(0);
231         assertTrue("Validation error is incorrect", error.getMessage().endsWith("does not conform to standard validation for field type."));
232 
233         searchAttribute = getAttribute("XMLSearchableAttributeStdDateTimeRange");
234         paramMap = new HashMap<Object, Object>();
235         paramMap.put(KewApiConstants.SearchableAttributeConstants.RANGE_UPPER_BOUND_PROPERTY_PREFIX + TestXMLSearchableAttributeDateTime.SEARCH_STORAGE_KEY, DocSearchUtils.getDisplayValueWithDateOnly(TestXMLSearchableAttributeDateTime.SEARCH_STORAGE_VALUE));
236         validationErrors = searchAttribute.validateUserSearchInputs(paramMap, context);
237         assertEquals("Validation should not have returned an error.", 0, validationErrors.size());
238         paramMap.clear();
239         paramMap.put(KewApiConstants.SearchableAttributeConstants.RANGE_LOWER_BOUND_PROPERTY_PREFIX + TestXMLSearchableAttributeDateTime.SEARCH_STORAGE_KEY, "001/5/08");
240         validationErrors = searchAttribute.validateUserSearchInputs(paramMap, context);
241         assertEquals("Validation should not have returned an error.", 0, validationErrors.size());
242         paramMap.clear();
243         paramMap.put(KewApiConstants.SearchableAttributeConstants.RANGE_LOWER_BOUND_PROPERTY_PREFIX + TestXMLSearchableAttributeDateTime.SEARCH_STORAGE_KEY, "41/5/08");
244         validationErrors = searchAttribute.validateUserSearchInputs(paramMap, context);
245         assertEquals("Validation should return a single error message.", 1, validationErrors.size());
246         error = validationErrors.get(0);
247         assertTrue("Validation error is incorrect", error.getMessage().endsWith("does not conform to standard validation for field type."));
248         paramMap.clear();
249         paramMap.put(KewApiConstants.SearchableAttributeConstants.RANGE_UPPER_BOUND_PROPERTY_PREFIX + TestXMLSearchableAttributeDateTime.SEARCH_STORAGE_KEY, "01/02/20*");
250         validationErrors = searchAttribute.validateUserSearchInputs(paramMap, context);
251         assertEquals("Validation should return a single error message.", 1, validationErrors.size());
252         error = validationErrors.get(0);
253         assertTrue("Validation error is incorrect", error.getMessage().endsWith("does not conform to standard validation for field type."));
254     }
255 
256     */
257     
258     /**
259      * Test searching by searchable attributes that use ranges
260      */
261     @Test public void testSearchableAttributeRanges() throws Exception {
262         String documentTypeName = "SearchDocTypeRangeSearchDataType";
263     	DocumentType docType = ((DocumentTypeService)KEWServiceLocator.getService(KEWServiceLocator.DOCUMENT_TYPE_SERVICE)).findByName(documentTypeName);
264         String userNetworkId = "rkirkend";
265         WorkflowDocument workflowDocument = WorkflowDocumentFactory.createDocument(getPrincipalIdForName(userNetworkId), documentTypeName);
266 
267         /*
268          *   Below we are using the keys and values from the custom searchable attribute classes' static constants but
269          *   this is only for convenience as those should always be valid values to test for.
270          */
271         // adding string searchable attribute
272         WorkflowAttributeDefinition.Builder stringXMLDef = WorkflowAttributeDefinition.Builder.create("XMLSearchableAttributeStringRange");
273         stringXMLDef.addPropertyDefinition(TestXMLSearchableAttributeString.SEARCH_STORAGE_KEY, TestXMLSearchableAttributeString.SEARCH_STORAGE_VALUE);
274         workflowDocument.addSearchableDefinition(stringXMLDef.build());
275         // adding long searchable attribute
276         WorkflowAttributeDefinition.Builder longXMLDef = WorkflowAttributeDefinition.Builder.create("XMLSearchableAttributeStdLongRange");
277         longXMLDef.addPropertyDefinition(TestXMLSearchableAttributeLong.SEARCH_STORAGE_KEY, TestXMLSearchableAttributeLong.SEARCH_STORAGE_VALUE.toString());
278         workflowDocument.addSearchableDefinition(longXMLDef.build());
279         // adding float searchable attribute
280         WorkflowAttributeDefinition.Builder floatXMLDef = WorkflowAttributeDefinition.Builder.create("XMLSearchableAttributeStdFloatRange");
281         floatXMLDef.addPropertyDefinition(TestXMLSearchableAttributeFloat.SEARCH_STORAGE_KEY, TestXMLSearchableAttributeFloat.SEARCH_STORAGE_VALUE.toString());
282         workflowDocument.addSearchableDefinition(floatXMLDef.build());
283         // adding string searchable attribute
284         WorkflowAttributeDefinition.Builder dateXMLDef = WorkflowAttributeDefinition.Builder.create("XMLSearchableAttributeStdDateTimeRange");
285         dateXMLDef.addPropertyDefinition(TestXMLSearchableAttributeDateTime.SEARCH_STORAGE_KEY, DocumentSearchInternalUtils
286                 .getDisplayValueWithDateOnly(TestXMLSearchableAttributeDateTime.SEARCH_STORAGE_VALUE));
287         workflowDocument.addSearchableDefinition(dateXMLDef.build());
288 
289         workflowDocument.setTitle("Routing style");
290         workflowDocument.route("routing this document.");
291 
292         workflowDocument = WorkflowDocumentFactory.loadDocument(getPrincipalIdForName(userNetworkId), workflowDocument.getDocumentId());
293 
294         DocumentSearchService docSearchService = (DocumentSearchService) KEWServiceLocator.getService(KEWServiceLocator.DOCUMENT_SEARCH_SERVICE);
295         Person user = KimApiServiceLocator.getPersonService().getPersonByPrincipalName(userNetworkId);
296 
297         // begin string attribute value testing
298         DocumentSearchCriteria.Builder criteria = null;
299         DocumentSearchResults results = null;
300 
301         criteria = DocumentSearchCriteria.Builder.create();
302         criteria.setDocumentTypeName(documentTypeName);
303         addSearchableAttributeRange(criteria, TestXMLSearchableAttributeString.SEARCH_STORAGE_KEY, TestXMLSearchableAttributeString.SEARCH_STORAGE_VALUE, null, false);
304         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
305         int size = results.getSearchResults().size();
306         assertTrue("Searching for a lower bound of 'jack'. case insensitive, inclusive.  so searching for something >= 'JACK'. Should Return 1, but got" + size, 1 == size);
307 
308         // begin long attribute value testing
309         // inclusive = true
310         String searchAttributeLongKey = TestXMLSearchableAttributeLong.SEARCH_STORAGE_KEY;
311         Long searchAttributeLongValue = TestXMLSearchableAttributeLong.SEARCH_STORAGE_VALUE.longValue();
312         Long longValueToUse = null;
313 
314         // test lower bound only
315         criteria = DocumentSearchCriteria.Builder.create();
316         criteria.setDocumentTypeName(documentTypeName);
317         longValueToUse = searchAttributeLongValue;
318         addSearchableAttributeRange(criteria, searchAttributeLongKey, longValueToUse.toString(), null, false);
319         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
320         assertEquals("Search results should have one document.", 1, results.getSearchResults().size());
321         
322         criteria = DocumentSearchCriteria.Builder.create();
323         criteria.setDocumentTypeName(documentTypeName);
324         longValueToUse = Long.valueOf(searchAttributeLongValue.longValue() - 1);
325         addSearchableAttributeRange(criteria, searchAttributeLongKey, longValueToUse.toString(), null, false);
326         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
327         assertEquals("Search results should have one document.", 1, results.getSearchResults().size());
328         
329         criteria = DocumentSearchCriteria.Builder.create();
330         criteria.setDocumentTypeName(documentTypeName);
331         longValueToUse = Long.valueOf(searchAttributeLongValue.longValue() + 1);
332         addSearchableAttributeRange(criteria, searchAttributeLongKey, longValueToUse.toString(), null, false);
333         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
334         assertEquals("Search results should have one document.", 0, results.getSearchResults().size());
335 
336         // test upper bound only
337         criteria = DocumentSearchCriteria.Builder.create();
338         criteria.setDocumentTypeName(documentTypeName);
339         longValueToUse = searchAttributeLongValue;
340         addSearchableAttributeRange(criteria, searchAttributeLongKey, null, longValueToUse.toString(), true);
341         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
342         assertEquals("Search results should have one document.", 1, results.getSearchResults().size());
343 
344         criteria = DocumentSearchCriteria.Builder.create();
345         criteria.setDocumentTypeName(documentTypeName);
346         longValueToUse = Long.valueOf(searchAttributeLongValue.longValue() - 1);
347         addSearchableAttributeRange(criteria, searchAttributeLongKey, null, longValueToUse.toString(), true);
348         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
349         assertEquals("Search results should have one document.", 0, results.getSearchResults().size());
350 
351         criteria = DocumentSearchCriteria.Builder.create();
352         criteria.setDocumentTypeName(documentTypeName);
353         longValueToUse = Long.valueOf(searchAttributeLongValue.longValue() + 1);
354         addSearchableAttributeRange(criteria, searchAttributeLongKey, null, longValueToUse.toString(), true);
355         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
356         assertEquals("Search results should have one document.", 1, results.getSearchResults().size());
357 
358         // test both bounds
359         criteria = DocumentSearchCriteria.Builder.create();
360         criteria.setDocumentTypeName(documentTypeName);
361         addSearchableAttributeRange(criteria, searchAttributeLongKey, Long.valueOf(searchAttributeLongValue.longValue())
362                 .toString(), Long.valueOf(searchAttributeLongValue.longValue()).toString(), true);
363         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
364         assertEquals("Search results should have one document.", 1, results.getSearchResults().size());
365 
366         criteria = DocumentSearchCriteria.Builder.create();
367         criteria.setDocumentTypeName(documentTypeName);
368         addSearchableAttributeRange(criteria, searchAttributeLongKey, Long.valueOf(
369                 searchAttributeLongValue.longValue() + 2).toString(), Long.valueOf(
370                 searchAttributeLongValue.longValue() + 4).toString(), true);
371         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
372         assertEquals("Search results should have one document.", 0, results.getSearchResults().size());
373         
374         criteria = DocumentSearchCriteria.Builder.create();
375         criteria.setDocumentTypeName(documentTypeName);
376         addSearchableAttributeRange(criteria, searchAttributeLongKey, Long.valueOf(
377                 searchAttributeLongValue.longValue() - 2).toString(), Long.valueOf(
378                 searchAttributeLongValue.longValue() - 4).toString(), true);
379         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
380         assertEquals("Search results should have one document.", 0, results.getSearchResults().size());
381 
382         criteria = DocumentSearchCriteria.Builder.create();
383         criteria.setDocumentTypeName(documentTypeName);
384         addSearchableAttributeRange(criteria, searchAttributeLongKey, Long.valueOf(
385                 searchAttributeLongValue.longValue() - 2).toString(), Long.valueOf(
386                 searchAttributeLongValue.longValue() + 2).toString(), true);
387         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
388         assertEquals("Search results should have one document.", 1, results.getSearchResults().size());
389 
390         criteria = DocumentSearchCriteria.Builder.create();
391         criteria.setDocumentTypeName(documentTypeName);
392         addSearchableAttributeRange(criteria, searchAttributeLongKey, Long.valueOf(searchAttributeLongValue.longValue() + 2).toString(), Long.valueOf(searchAttributeLongValue.longValue() - 2).toString(), true);
393         try {
394             docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
395             fail("Error should have been thrown for invalid range");
396         } catch (WorkflowServiceErrorException e) {}
397 
398         // begin float attribute value testing
399         String searchAttributeFloatKey = TestXMLSearchableAttributeFloat.SEARCH_STORAGE_KEY;
400         BigDecimal searchAttributeFloatValue = TestXMLSearchableAttributeFloat.SEARCH_STORAGE_VALUE;
401 
402         BigDecimal floatValueToUse = null;
403         // test lower bound only
404         criteria = DocumentSearchCriteria.Builder.create();
405         criteria.setDocumentTypeName(documentTypeName);
406         floatValueToUse = searchAttributeFloatValue;
407         addSearchableAttributeRange(criteria, searchAttributeFloatKey, floatValueToUse.toString(), null, false);
408         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
409         assertEquals("Search results should have one document.", 0, results.getSearchResults().size());
410 
411         criteria = DocumentSearchCriteria.Builder.create();
412         criteria.setDocumentTypeName(documentTypeName);
413         floatValueToUse = searchAttributeFloatValue.subtract(BigDecimal.ONE);
414         addSearchableAttributeRange(criteria, searchAttributeFloatKey, floatValueToUse.toString(), null, false);
415         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
416         assertEquals("Search results should have one document.", 1, results.getSearchResults().size());
417 
418         criteria = DocumentSearchCriteria.Builder.create();
419         criteria.setDocumentTypeName(documentTypeName);
420         floatValueToUse = searchAttributeFloatValue.add(BigDecimal.ONE);
421         addSearchableAttributeRange(criteria, searchAttributeFloatKey, floatValueToUse.toString(), null, false);
422         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
423         assertEquals("Search results should have one document.", 0, results.getSearchResults().size());
424 
425         // test upper bound only
426         criteria = DocumentSearchCriteria.Builder.create();
427         criteria.setDocumentTypeName(documentTypeName);
428         floatValueToUse = searchAttributeFloatValue;
429         addSearchableAttributeRange(criteria, searchAttributeFloatKey, null, floatValueToUse.toString(), true);
430         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
431         assertEquals("Search results should have one document.", 0, results.getSearchResults().size());
432 
433         criteria = DocumentSearchCriteria.Builder.create();
434         criteria.setDocumentTypeName(documentTypeName);
435         floatValueToUse = searchAttributeFloatValue.subtract(BigDecimal.ONE);
436         addSearchableAttributeRange(criteria, searchAttributeFloatKey, null, floatValueToUse.toString(), true);
437         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
438         assertEquals("Search results should have one document.", 0, results.getSearchResults().size());
439 
440         criteria = DocumentSearchCriteria.Builder.create();
441         criteria.setDocumentTypeName(documentTypeName);
442         floatValueToUse = searchAttributeFloatValue.add(BigDecimal.ONE);
443         addSearchableAttributeRange(criteria, searchAttributeFloatKey, null, floatValueToUse.toString(), true);
444         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
445         assertEquals("Search results should have one document.", 1, results.getSearchResults().size());
446 
447         // test both bounds
448         criteria = DocumentSearchCriteria.Builder.create();
449         criteria.setDocumentTypeName(documentTypeName);
450         addSearchableAttributeRange(criteria, searchAttributeFloatKey, searchAttributeFloatValue.toString(),
451                 searchAttributeFloatValue.toString(), true);
452         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
453         assertEquals("Search results should have one document.", 0, results.getSearchResults().size());
454 
455         criteria = DocumentSearchCriteria.Builder.create();
456         criteria.setDocumentTypeName(documentTypeName);
457         addSearchableAttributeRange(criteria, searchAttributeFloatKey, (searchAttributeFloatValue.add(new BigDecimal(
458                 2))).toString(), (searchAttributeFloatValue.add(new BigDecimal(4))).toString(), true);
459         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
460         assertEquals("Search results should have one document.", 0, results.getSearchResults().size());
461 
462         criteria = DocumentSearchCriteria.Builder.create();
463         criteria.setDocumentTypeName(documentTypeName);
464         addSearchableAttributeRange(criteria, searchAttributeFloatKey, (searchAttributeFloatValue.subtract(
465                 new BigDecimal(4))).toString(), (searchAttributeFloatValue.subtract(new BigDecimal(2))).toString(),
466                 true);
467         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
468         assertEquals("Search results should have one document.", 0, results.getSearchResults().size());
469 
470         criteria = DocumentSearchCriteria.Builder.create();
471         criteria.setDocumentTypeName(documentTypeName);
472         addSearchableAttributeRange(criteria, searchAttributeFloatKey, (searchAttributeFloatValue.subtract(new BigDecimal(2))).toString(), (searchAttributeFloatValue.add(new BigDecimal(2))).toString(), true);
473         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
474         assertEquals("Search results should have one document.", 1, results.getSearchResults().size());
475 
476         criteria = DocumentSearchCriteria.Builder.create();
477         criteria.setDocumentTypeName(documentTypeName);
478         addSearchableAttributeRange(criteria, searchAttributeFloatKey, (searchAttributeFloatValue.add(new BigDecimal(
479                 2))).toString(), (searchAttributeFloatValue.subtract(new BigDecimal(2))).toString(), true);
480         try {
481             docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
482             fail("Error should have been thrown for invalid range");
483         } catch (WorkflowServiceErrorException e) {}
484 
485         // begin datetime attribute value testing
486         // inclusive = ?
487         String searchAttributeDateTimeKey = TestXMLSearchableAttributeDateTime.SEARCH_STORAGE_KEY;
488         Calendar searchAttributeDateTimeValue = TestXMLSearchableAttributeDateTime.SEARCH_STORAGE_VALUE.toGregorianCalendar();
489 
490         Calendar calendarValueToUse = null;
491         // test lower bound only
492         criteria = DocumentSearchCriteria.Builder.create();
493         criteria.setDocumentTypeName(documentTypeName);
494         calendarValueToUse = (Calendar) searchAttributeDateTimeValue.clone();
495         String valueToSearch = DocumentSearchInternalUtils.getDisplayValueWithDateOnly(SQLUtils.convertCalendar(
496                 calendarValueToUse));
497         addSearchableAttributeRange(criteria, searchAttributeDateTimeKey, valueToSearch, null, false);
498         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
499         assertEquals("Search results should have one document.", 0, results.getSearchResults().size());
500 
501         criteria = DocumentSearchCriteria.Builder.create();
502         criteria.setDocumentTypeName(documentTypeName);
503         calendarValueToUse = (Calendar) searchAttributeDateTimeValue.clone();
504         calendarValueToUse.add(Calendar.DATE, -1);
505         valueToSearch = DocumentSearchInternalUtils.getDisplayValueWithDateOnly(SQLUtils.convertCalendar(
506                 calendarValueToUse));
507         addSearchableAttributeRange(criteria, searchAttributeDateTimeKey, valueToSearch, null, false);
508         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
509         assertEquals("Search results should have one document.", 1, results.getSearchResults().size());
510 
511         criteria = DocumentSearchCriteria.Builder.create();
512         criteria.setDocumentTypeName(documentTypeName);
513         calendarValueToUse = (Calendar) searchAttributeDateTimeValue.clone();
514         calendarValueToUse.add(Calendar.DATE, 1);
515         valueToSearch = DocumentSearchInternalUtils.getDisplayValueWithDateOnly(SQLUtils.convertCalendar(
516                 calendarValueToUse));
517         addSearchableAttributeRange(criteria, searchAttributeDateTimeKey, valueToSearch, null, false);
518         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
519         assertEquals("Search results should have one document.", 0, results.getSearchResults().size());
520 
521         // test upper bound only
522         criteria = DocumentSearchCriteria.Builder.create();
523         criteria.setDocumentTypeName(documentTypeName);
524         calendarValueToUse = (Calendar) searchAttributeDateTimeValue.clone();
525         addSearchableAttributeRange(criteria, searchAttributeDateTimeKey, null,
526                 DocumentSearchInternalUtils.getDisplayValueWithDateOnly(SQLUtils.convertCalendar(calendarValueToUse)), true);
527         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
528         assertEquals("Search results should have one document.", 1, results.getSearchResults().size());
529 
530         criteria = DocumentSearchCriteria.Builder.create();
531         criteria.setDocumentTypeName(documentTypeName);
532         calendarValueToUse = (Calendar) searchAttributeDateTimeValue.clone();
533         calendarValueToUse.add(Calendar.DATE, -1);
534         addSearchableAttributeRange(criteria, searchAttributeDateTimeKey, null, DocumentSearchInternalUtils
535                 .getDisplayValueWithDateOnly(SQLUtils.convertCalendar(calendarValueToUse)), true);
536         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
537         assertEquals("Search results should have one document.", 0, results.getSearchResults().size());
538 
539         criteria = DocumentSearchCriteria.Builder.create();
540         criteria.setDocumentTypeName(documentTypeName);
541         calendarValueToUse = (Calendar) searchAttributeDateTimeValue.clone();
542         calendarValueToUse.add(Calendar.DATE, 1);
543         addSearchableAttributeRange(criteria, searchAttributeDateTimeKey, null,
544                 DocumentSearchInternalUtils.getDisplayValueWithDateOnly(SQLUtils.convertCalendar(calendarValueToUse)), true);
545         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
546         assertEquals("Search results should have one document.", 1, results.getSearchResults().size());
547 
548         // test both bounds
549         criteria = DocumentSearchCriteria.Builder.create();
550         criteria.setDocumentTypeName(documentTypeName);
551         Calendar lowerBoundValue = (Calendar) searchAttributeDateTimeValue.clone();
552         Calendar upperBoundValue = (Calendar) searchAttributeDateTimeValue.clone();
553         addSearchableAttributeRange(criteria, searchAttributeDateTimeKey, DocumentSearchInternalUtils
554                 .getDisplayValueWithDateOnly(SQLUtils.convertCalendar(lowerBoundValue)), DocumentSearchInternalUtils
555                 .getDisplayValueWithDateOnly(SQLUtils.convertCalendar(upperBoundValue)), true);
556         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
557         assertEquals("Search results should have one document.", 0, results.getSearchResults().size());
558 
559         criteria = DocumentSearchCriteria.Builder.create();
560         criteria.setDocumentTypeName(documentTypeName);
561         lowerBoundValue = (Calendar) searchAttributeDateTimeValue.clone();
562         lowerBoundValue.add(Calendar.DATE, 2);
563         upperBoundValue = (Calendar) searchAttributeDateTimeValue.clone();
564         upperBoundValue.add(Calendar.DATE, 4);
565         addSearchableAttributeRange(criteria, searchAttributeDateTimeKey, DocumentSearchInternalUtils
566                 .getDisplayValueWithDateOnly(SQLUtils.convertCalendar(lowerBoundValue)), DocumentSearchInternalUtils
567                 .getDisplayValueWithDateOnly(SQLUtils.convertCalendar(upperBoundValue)), true);
568         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
569         assertEquals("Search results should have one document.", 0, results.getSearchResults().size());
570 
571         criteria = DocumentSearchCriteria.Builder.create();
572         criteria.setDocumentTypeName(documentTypeName);
573         lowerBoundValue = (Calendar) searchAttributeDateTimeValue.clone();
574         lowerBoundValue.add(Calendar.DATE, -4);
575         upperBoundValue = (Calendar) searchAttributeDateTimeValue.clone();
576         upperBoundValue.add(Calendar.DATE, -2);
577         addSearchableAttributeRange(criteria, searchAttributeDateTimeKey, DocumentSearchInternalUtils
578                 .getDisplayValueWithDateOnly(SQLUtils.convertCalendar(lowerBoundValue)), DocumentSearchInternalUtils
579                 .getDisplayValueWithDateOnly(SQLUtils.convertCalendar(upperBoundValue)), true);
580         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
581         assertEquals("Search results should have one document.", 0, results.getSearchResults().size());
582 
583         criteria = DocumentSearchCriteria.Builder.create();
584         criteria.setDocumentTypeName(documentTypeName);
585         lowerBoundValue = (Calendar) searchAttributeDateTimeValue.clone();
586         lowerBoundValue.add(Calendar.DATE, -2);
587         upperBoundValue = (Calendar) searchAttributeDateTimeValue.clone();
588         upperBoundValue.add(Calendar.DATE, 2);
589         addSearchableAttributeRange(criteria, searchAttributeDateTimeKey, DocumentSearchInternalUtils
590                 .getDisplayValueWithDateOnly(SQLUtils.convertCalendar(lowerBoundValue)), DocumentSearchInternalUtils
591                 .getDisplayValueWithDateOnly(SQLUtils.convertCalendar(upperBoundValue)), true);
592         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
593         assertEquals("Search results should have one document.", 1, results.getSearchResults().size());
594 
595         criteria = DocumentSearchCriteria.Builder.create();
596         criteria.setDocumentTypeName(documentTypeName);
597         lowerBoundValue = (Calendar) searchAttributeDateTimeValue.clone();
598         lowerBoundValue.add(Calendar.DATE, 2);
599         upperBoundValue = (Calendar) searchAttributeDateTimeValue.clone();
600         upperBoundValue.add(Calendar.DATE, -2);
601         addSearchableAttributeRange(criteria, searchAttributeDateTimeKey, DocumentSearchInternalUtils
602                 .getDisplayValueWithDateOnly(SQLUtils.convertCalendar(lowerBoundValue)), DocumentSearchInternalUtils
603                 .getDisplayValueWithDateOnly(SQLUtils.convertCalendar(upperBoundValue)), true);
604         try {
605             docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
606             fail("Error should have been thrown for invalid range");
607         } catch (WorkflowServiceErrorException e) {}
608     }
609 
610     /*
611      * Tests the XML string attributes on range definitions, using a technique similar to that of the testSearchableAttributeRanges() unit test.
612      */
613     @Test public void testRangeDefinitionStringAttributes() throws Exception {
614         String documentTypeName = "RangeDefinitionTestDocType";
615     	DocumentType docType = KEWServiceLocator.getDocumentTypeService().findByName(documentTypeName);
616         String principalName = "rkirkend";
617         String principalId = KimApiServiceLocator.getPersonService().getPersonByPrincipalName(principalName).getPrincipalId();
618         WorkflowDocument workflowDocument = WorkflowDocumentFactory.createDocument(principalId, documentTypeName);
619 
620         // adding inclusive-lower-bound searchable attribute
621         WorkflowAttributeDefinition.Builder inclusiveLowerXMLDef = WorkflowAttributeDefinition.Builder.create("TextFieldWithInclusiveLower");
622         inclusiveLowerXMLDef.addPropertyDefinition("textFieldWithInclusiveLower", "newvalue");
623         workflowDocument.addSearchableDefinition(inclusiveLowerXMLDef.build());
624         // adding case-sensitive searchable attribute
625         WorkflowAttributeDefinition.Builder caseSensitiveXMLDef = WorkflowAttributeDefinition.Builder.create("TextFieldWithCaseSensitivity");
626         caseSensitiveXMLDef.addPropertyDefinition("textFieldWithCaseSensitivity", "thevalue");
627         workflowDocument.addSearchableDefinition(caseSensitiveXMLDef.build());
628         // adding searchable attribute with overridden properties
629         WorkflowAttributeDefinition.Builder overridesXMLDef = WorkflowAttributeDefinition.Builder.create("TextFieldWithOverrides");
630         overridesXMLDef.addPropertyDefinition("textFieldWithOverrides", "SomeVal");
631         workflowDocument.addSearchableDefinition(overridesXMLDef.build());
632 
633         workflowDocument.setTitle("Range Def Test");
634         workflowDocument.route("routing range def doc.");
635 
636         workflowDocument = WorkflowDocumentFactory.loadDocument(principalId, workflowDocument.getDocumentId());
637 
638         // Verify that the "TextFieldWithInclusiveLower" attribute behaves as expected (lower-bound-inclusive and (by default) case-insensitive).
639         assertSearchBehavesAsExpected(docType, principalId, "textFieldWithInclusiveLower",
640         		new String[] { "newvalue", ""        , ""        , "NEWVALUD", "newValuf", "newValuj", "newvaluf"},
641         		new String[] { ""        , "newvalue", "Newvaluf", "NEWVALUF", "newValud", "NEWVALUK", ""        },
642         		new int[]    { 1         , 0         , 1         , 1         , -1        , 0         , 0         });
643 
644         // Verify that the "TextFieldWithCaseSensitivity" attribute behaves as expected (bound-inclusive and case-sensitive).
645         assertSearchBehavesAsExpected(docType, principalId, "textFieldWithCaseSensitivity",
646         		new String[] { "thevalue", ""        , ""        , "THEVALUD", "thevalud", "Thevalud", "THEVALUF"},
647         		new String[] { ""        , "thevalue", "Thevalue", "THEVALUF", "THEVALUF", "Thevaluf", ""        },
648         		new int[]    { 1         , 1         , 0         , 0         , -1        , 0         , 1         });
649 
650         // Verify that the "TextFieldWithOverrides" attribute behaves as expected (that is, after overriding the appropriate region definition
651         // properties, the lower bound should be case-insensitive and non-inclusive, and the upper bound should be case-sensitive and inclusive).
652         assertSearchBehavesAsExpected(docType, principalId, "textFieldWithOverrides",
653         		new String[] { "someval", "SomeVal", ""       , ""       , "SOMEVAK", "SomeVam", "SOMEVAM", "somevak", ""       },
654         		new String[] { ""       , ""       , "SOMEVAL", "SomeVal", "SomeVam", "SOMEVAK", "SomeVak", ""       , "SomeVak"},
655         		new int[]    { 0        , 0        , 0        , 1        , 1        , 0       , 0        , 1        , 0        });
656     }
657 
658     /*
659      * A convenience method for performing document-searching operations involving range definitions. The array parameters must all be the same length,
660      * since this method will perform tests with the values given by entries located at the same indices.
661      * @param docType The document type to search for.
662      * @param principalId The ID of the user that will perform the search.
663      * @param fieldDefKey The name of the field given by the field definition on the searchable attribute.
664      * @param lowBounds The lower bounds to use in the tests; to ignore a lower bound for a test, use an empty String.
665      * @param upBounds The upper bounds to use in the tests; to ignore an upper bound for a test, use an empty String.
666      * @param resultSizes The expected number of documents to be returned by the search; use -1 to indicate that an exception should have occurred.
667      * @throws Exception
668      */
669     private void assertSearchBehavesAsExpected(DocumentType docType, String principalId, String fieldDefKey, String[] lowBounds, String[] upBounds,
670     		int[] resultSizes) throws Exception {
671         DocumentSearchCriteria.Builder criteria = null;
672         DocumentSearchResults results = null;
673         DocumentSearchService docSearchService = KEWServiceLocator.getDocumentSearchService();
674         for (int i = 0; i < resultSizes.length; i++) {
675         	criteria = DocumentSearchCriteria.Builder.create();
676         	criteria.setDocumentTypeName(docType.getName());
677             addSearchableAttributeRange(criteria, fieldDefKey, lowBounds[i], upBounds[i], true);
678         	try {
679         		results = docSearchService.lookupDocuments(principalId, criteria.build());
680         		if (resultSizes[i] < 0) {
681         			fail(fieldDefKey + "'s search at loop index " + i + " should have thrown an exception");
682         		}
683         		assertEquals(fieldDefKey
684                         + "'s search results at loop index "
685                         + i
686                         + " returned the wrong number of documents.", resultSizes[i], results.getSearchResults().size());
687         	} catch (Exception ex) {
688         		if (resultSizes[i] >= 0) {
689         			fail(fieldDefKey + "'s search at loop index " + i + " should not have thrown an exception");
690         		}
691         	}
692         }
693     }
694 }