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             // KULRICE-5630 fails because SGXSearchableAttribute does detect ranges correctly yet
395             docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
396             fail("Error should have been thrown for invalid range");
397         } catch (WorkflowServiceErrorException e) {}
398 
399         // begin float attribute value testing
400         String searchAttributeFloatKey = TestXMLSearchableAttributeFloat.SEARCH_STORAGE_KEY;
401         BigDecimal searchAttributeFloatValue = TestXMLSearchableAttributeFloat.SEARCH_STORAGE_VALUE;
402 
403         BigDecimal floatValueToUse = null;
404         // test lower bound only
405         criteria = DocumentSearchCriteria.Builder.create();
406         criteria.setDocumentTypeName(documentTypeName);
407         floatValueToUse = searchAttributeFloatValue;
408         addSearchableAttributeRange(criteria, searchAttributeFloatKey, floatValueToUse.toString(), null, false);
409         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
410         assertEquals("Search results should have one document.", 0, results.getSearchResults().size());
411 
412         criteria = DocumentSearchCriteria.Builder.create();
413         criteria.setDocumentTypeName(documentTypeName);
414         floatValueToUse = searchAttributeFloatValue.subtract(BigDecimal.ONE);
415         addSearchableAttributeRange(criteria, searchAttributeFloatKey, floatValueToUse.toString(), null, false);
416         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
417         assertEquals("Search results should have one document.", 1, results.getSearchResults().size());
418 
419         criteria = DocumentSearchCriteria.Builder.create();
420         criteria.setDocumentTypeName(documentTypeName);
421         floatValueToUse = searchAttributeFloatValue.add(BigDecimal.ONE);
422         addSearchableAttributeRange(criteria, searchAttributeFloatKey, floatValueToUse.toString(), null, false);
423         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
424         assertEquals("Search results should have one document.", 0, results.getSearchResults().size());
425 
426         // test upper bound only
427         criteria = DocumentSearchCriteria.Builder.create();
428         criteria.setDocumentTypeName(documentTypeName);
429         floatValueToUse = searchAttributeFloatValue;
430         addSearchableAttributeRange(criteria, searchAttributeFloatKey, null, floatValueToUse.toString(), true);
431         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
432         assertEquals("Search results should have one document.", 0, results.getSearchResults().size());
433 
434         criteria = DocumentSearchCriteria.Builder.create();
435         criteria.setDocumentTypeName(documentTypeName);
436         floatValueToUse = searchAttributeFloatValue.subtract(BigDecimal.ONE);
437         addSearchableAttributeRange(criteria, searchAttributeFloatKey, null, floatValueToUse.toString(), true);
438         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
439         assertEquals("Search results should have one document.", 0, results.getSearchResults().size());
440 
441         criteria = DocumentSearchCriteria.Builder.create();
442         criteria.setDocumentTypeName(documentTypeName);
443         floatValueToUse = searchAttributeFloatValue.add(BigDecimal.ONE);
444         addSearchableAttributeRange(criteria, searchAttributeFloatKey, null, floatValueToUse.toString(), true);
445         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
446         assertEquals("Search results should have one document.", 1, results.getSearchResults().size());
447 
448         // test both bounds
449         criteria = DocumentSearchCriteria.Builder.create();
450         criteria.setDocumentTypeName(documentTypeName);
451         addSearchableAttributeRange(criteria, searchAttributeFloatKey, searchAttributeFloatValue.toString(),
452                 searchAttributeFloatValue.toString(), true);
453         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
454         assertEquals("Search results should have one document.", 0, results.getSearchResults().size());
455 
456         criteria = DocumentSearchCriteria.Builder.create();
457         criteria.setDocumentTypeName(documentTypeName);
458         addSearchableAttributeRange(criteria, searchAttributeFloatKey, (searchAttributeFloatValue.add(new BigDecimal(
459                 2))).toString(), (searchAttributeFloatValue.add(new BigDecimal(4))).toString(), true);
460         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
461         assertEquals("Search results should have one document.", 0, results.getSearchResults().size());
462 
463         criteria = DocumentSearchCriteria.Builder.create();
464         criteria.setDocumentTypeName(documentTypeName);
465         addSearchableAttributeRange(criteria, searchAttributeFloatKey, (searchAttributeFloatValue.subtract(
466                 new BigDecimal(4))).toString(), (searchAttributeFloatValue.subtract(new BigDecimal(2))).toString(),
467                 true);
468         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
469         assertEquals("Search results should have one document.", 0, results.getSearchResults().size());
470 
471         criteria = DocumentSearchCriteria.Builder.create();
472         criteria.setDocumentTypeName(documentTypeName);
473         addSearchableAttributeRange(criteria, searchAttributeFloatKey, (searchAttributeFloatValue.subtract(new BigDecimal(2))).toString(), (searchAttributeFloatValue.add(new BigDecimal(2))).toString(), true);
474         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
475         assertEquals("Search results should have one document.", 1, results.getSearchResults().size());
476 
477         criteria = DocumentSearchCriteria.Builder.create();
478         criteria.setDocumentTypeName(documentTypeName);
479         addSearchableAttributeRange(criteria, searchAttributeFloatKey, (searchAttributeFloatValue.add(new BigDecimal(
480                 2))).toString(), (searchAttributeFloatValue.subtract(new BigDecimal(2))).toString(), true);
481         try {
482             docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
483             fail("Error should have been thrown for invalid range");
484         } catch (WorkflowServiceErrorException e) {}
485 
486         // begin datetime attribute value testing
487         // inclusive = ?
488         String searchAttributeDateTimeKey = TestXMLSearchableAttributeDateTime.SEARCH_STORAGE_KEY;
489         Calendar searchAttributeDateTimeValue = TestXMLSearchableAttributeDateTime.SEARCH_STORAGE_VALUE.toGregorianCalendar();
490 
491         Calendar calendarValueToUse = null;
492         // test lower bound only
493         criteria = DocumentSearchCriteria.Builder.create();
494         criteria.setDocumentTypeName(documentTypeName);
495         calendarValueToUse = (Calendar) searchAttributeDateTimeValue.clone();
496         String valueToSearch = DocumentSearchInternalUtils.getDisplayValueWithDateOnly(SQLUtils.convertCalendar(
497                 calendarValueToUse));
498         addSearchableAttributeRange(criteria, searchAttributeDateTimeKey, valueToSearch, null, false);
499         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
500         assertEquals("Search results should have one document.", 0, results.getSearchResults().size());
501 
502         criteria = DocumentSearchCriteria.Builder.create();
503         criteria.setDocumentTypeName(documentTypeName);
504         calendarValueToUse = (Calendar) searchAttributeDateTimeValue.clone();
505         calendarValueToUse.add(Calendar.DATE, -1);
506         valueToSearch = DocumentSearchInternalUtils.getDisplayValueWithDateOnly(SQLUtils.convertCalendar(
507                 calendarValueToUse));
508         addSearchableAttributeRange(criteria, searchAttributeDateTimeKey, valueToSearch, null, false);
509         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
510         assertEquals("Search results should have one document.", 1, results.getSearchResults().size());
511 
512         criteria = DocumentSearchCriteria.Builder.create();
513         criteria.setDocumentTypeName(documentTypeName);
514         calendarValueToUse = (Calendar) searchAttributeDateTimeValue.clone();
515         calendarValueToUse.add(Calendar.DATE, 1);
516         valueToSearch = DocumentSearchInternalUtils.getDisplayValueWithDateOnly(SQLUtils.convertCalendar(
517                 calendarValueToUse));
518         addSearchableAttributeRange(criteria, searchAttributeDateTimeKey, valueToSearch, null, false);
519         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
520         assertEquals("Search results should have one document.", 0, results.getSearchResults().size());
521 
522         // test upper bound only
523         criteria = DocumentSearchCriteria.Builder.create();
524         criteria.setDocumentTypeName(documentTypeName);
525         calendarValueToUse = (Calendar) searchAttributeDateTimeValue.clone();
526         addSearchableAttributeRange(criteria, searchAttributeDateTimeKey, null,
527                 DocumentSearchInternalUtils.getDisplayValueWithDateOnly(SQLUtils.convertCalendar(calendarValueToUse)), true);
528         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
529         assertEquals("Search results should have one document.", 1, results.getSearchResults().size());
530 
531         criteria = DocumentSearchCriteria.Builder.create();
532         criteria.setDocumentTypeName(documentTypeName);
533         calendarValueToUse = (Calendar) searchAttributeDateTimeValue.clone();
534         calendarValueToUse.add(Calendar.DATE, -1);
535         addSearchableAttributeRange(criteria, searchAttributeDateTimeKey, null, DocumentSearchInternalUtils
536                 .getDisplayValueWithDateOnly(SQLUtils.convertCalendar(calendarValueToUse)), true);
537         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
538         assertEquals("Search results should have one document.", 0, results.getSearchResults().size());
539 
540         criteria = DocumentSearchCriteria.Builder.create();
541         criteria.setDocumentTypeName(documentTypeName);
542         calendarValueToUse = (Calendar) searchAttributeDateTimeValue.clone();
543         calendarValueToUse.add(Calendar.DATE, 1);
544         addSearchableAttributeRange(criteria, searchAttributeDateTimeKey, null,
545                 DocumentSearchInternalUtils.getDisplayValueWithDateOnly(SQLUtils.convertCalendar(calendarValueToUse)), true);
546         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
547         assertEquals("Search results should have one document.", 1, results.getSearchResults().size());
548 
549         // test both bounds
550         criteria = DocumentSearchCriteria.Builder.create();
551         criteria.setDocumentTypeName(documentTypeName);
552         Calendar lowerBoundValue = (Calendar) searchAttributeDateTimeValue.clone();
553         Calendar upperBoundValue = (Calendar) searchAttributeDateTimeValue.clone();
554         addSearchableAttributeRange(criteria, searchAttributeDateTimeKey, DocumentSearchInternalUtils
555                 .getDisplayValueWithDateOnly(SQLUtils.convertCalendar(lowerBoundValue)), DocumentSearchInternalUtils
556                 .getDisplayValueWithDateOnly(SQLUtils.convertCalendar(upperBoundValue)), true);
557         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
558         assertEquals("Search results should have one document.", 0, results.getSearchResults().size());
559 
560         criteria = DocumentSearchCriteria.Builder.create();
561         criteria.setDocumentTypeName(documentTypeName);
562         lowerBoundValue = (Calendar) searchAttributeDateTimeValue.clone();
563         lowerBoundValue.add(Calendar.DATE, 2);
564         upperBoundValue = (Calendar) searchAttributeDateTimeValue.clone();
565         upperBoundValue.add(Calendar.DATE, 4);
566         addSearchableAttributeRange(criteria, searchAttributeDateTimeKey, DocumentSearchInternalUtils
567                 .getDisplayValueWithDateOnly(SQLUtils.convertCalendar(lowerBoundValue)), DocumentSearchInternalUtils
568                 .getDisplayValueWithDateOnly(SQLUtils.convertCalendar(upperBoundValue)), true);
569         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
570         assertEquals("Search results should have one document.", 0, results.getSearchResults().size());
571 
572         criteria = DocumentSearchCriteria.Builder.create();
573         criteria.setDocumentTypeName(documentTypeName);
574         lowerBoundValue = (Calendar) searchAttributeDateTimeValue.clone();
575         lowerBoundValue.add(Calendar.DATE, -4);
576         upperBoundValue = (Calendar) searchAttributeDateTimeValue.clone();
577         upperBoundValue.add(Calendar.DATE, -2);
578         addSearchableAttributeRange(criteria, searchAttributeDateTimeKey, DocumentSearchInternalUtils
579                 .getDisplayValueWithDateOnly(SQLUtils.convertCalendar(lowerBoundValue)), DocumentSearchInternalUtils
580                 .getDisplayValueWithDateOnly(SQLUtils.convertCalendar(upperBoundValue)), true);
581         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
582         assertEquals("Search results should have one document.", 0, results.getSearchResults().size());
583 
584         criteria = DocumentSearchCriteria.Builder.create();
585         criteria.setDocumentTypeName(documentTypeName);
586         lowerBoundValue = (Calendar) searchAttributeDateTimeValue.clone();
587         lowerBoundValue.add(Calendar.DATE, -2);
588         upperBoundValue = (Calendar) searchAttributeDateTimeValue.clone();
589         upperBoundValue.add(Calendar.DATE, 2);
590         addSearchableAttributeRange(criteria, searchAttributeDateTimeKey, DocumentSearchInternalUtils
591                 .getDisplayValueWithDateOnly(SQLUtils.convertCalendar(lowerBoundValue)), DocumentSearchInternalUtils
592                 .getDisplayValueWithDateOnly(SQLUtils.convertCalendar(upperBoundValue)), true);
593         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
594         assertEquals("Search results should have one document.", 1, results.getSearchResults().size());
595 
596         criteria = DocumentSearchCriteria.Builder.create();
597         criteria.setDocumentTypeName(documentTypeName);
598         lowerBoundValue = (Calendar) searchAttributeDateTimeValue.clone();
599         lowerBoundValue.add(Calendar.DATE, 2);
600         upperBoundValue = (Calendar) searchAttributeDateTimeValue.clone();
601         upperBoundValue.add(Calendar.DATE, -2);
602         addSearchableAttributeRange(criteria, searchAttributeDateTimeKey, DocumentSearchInternalUtils
603                 .getDisplayValueWithDateOnly(SQLUtils.convertCalendar(lowerBoundValue)), DocumentSearchInternalUtils
604                 .getDisplayValueWithDateOnly(SQLUtils.convertCalendar(upperBoundValue)), true);
605         try {
606             docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
607             fail("Error should have been thrown for invalid range");
608         } catch (WorkflowServiceErrorException e) {}
609     }
610 
611     /*
612      * Tests the XML string attributes on range definitions, using a technique similar to that of the testSearchableAttributeRanges() unit test.
613      */
614     @Test public void testRangeDefinitionStringAttributes() throws Exception {
615         String documentTypeName = "RangeDefinitionTestDocType";
616     	DocumentType docType = KEWServiceLocator.getDocumentTypeService().findByName(documentTypeName);
617         String principalName = "rkirkend";
618         String principalId = KimApiServiceLocator.getPersonService().getPersonByPrincipalName(principalName).getPrincipalId();
619         WorkflowDocument workflowDocument = WorkflowDocumentFactory.createDocument(principalId, documentTypeName);
620 
621         // adding inclusive-lower-bound searchable attribute
622         WorkflowAttributeDefinition.Builder inclusiveLowerXMLDef = WorkflowAttributeDefinition.Builder.create("TextFieldWithInclusiveLower");
623         inclusiveLowerXMLDef.addPropertyDefinition("textFieldWithInclusiveLower", "newvalue");
624         workflowDocument.addSearchableDefinition(inclusiveLowerXMLDef.build());
625         // adding case-sensitive searchable attribute
626         WorkflowAttributeDefinition.Builder caseSensitiveXMLDef = WorkflowAttributeDefinition.Builder.create("TextFieldWithCaseSensitivity");
627         caseSensitiveXMLDef.addPropertyDefinition("textFieldWithCaseSensitivity", "thevalue");
628         workflowDocument.addSearchableDefinition(caseSensitiveXMLDef.build());
629         // adding searchable attribute with overridden properties
630         WorkflowAttributeDefinition.Builder overridesXMLDef = WorkflowAttributeDefinition.Builder.create("TextFieldWithOverrides");
631         overridesXMLDef.addPropertyDefinition("textFieldWithOverrides", "SomeVal");
632         workflowDocument.addSearchableDefinition(overridesXMLDef.build());
633 
634         workflowDocument.setTitle("Range Def Test");
635         workflowDocument.route("routing range def doc.");
636 
637         workflowDocument = WorkflowDocumentFactory.loadDocument(principalId, workflowDocument.getDocumentId());
638 
639         // Verify that the "TextFieldWithInclusiveLower" attribute behaves as expected (lower-bound-inclusive and (by default) case-insensitive).
640         assertSearchBehavesAsExpected(docType, principalId, "textFieldWithInclusiveLower",
641         		new String[] { "newvalue", ""        , ""        , "NEWVALUD", "newValuf", "newValuj", "newvaluf"},
642         		new String[] { ""        , "newvalue", "Newvaluf", "NEWVALUF", "newValud", "NEWVALUK", ""        },
643         		new int[]    { 1         , 0         , 1         , 1         , -1        , 0         , 0         });
644 
645         // Verify that the "TextFieldWithCaseSensitivity" attribute behaves as expected (bound-inclusive and case-sensitive).
646         assertSearchBehavesAsExpected(docType, principalId, "textFieldWithCaseSensitivity",
647         		new String[] { "thevalue", ""        , ""        , "THEVALUD", "thevalud", "Thevalud", "THEVALUF"},
648         		new String[] { ""        , "thevalue", "Thevalue", "THEVALUF", "THEVALUF", "Thevaluf", ""        },
649         		new int[]    { 1         , 1         , 0         , 0         , -1        , 0         , 1         });
650 
651         // Verify that the "TextFieldWithOverrides" attribute behaves as expected (that is, after overriding the appropriate region definition
652         // properties, the lower bound should be case-insensitive and non-inclusive, and the upper bound should be case-sensitive and inclusive).
653         assertSearchBehavesAsExpected(docType, principalId, "textFieldWithOverrides",
654         		new String[] { "someval", "SomeVal", ""       , ""       , "SOMEVAK", "SomeVam", "SOMEVAM", "somevak", ""       },
655         		new String[] { ""       , ""       , "SOMEVAL", "SomeVal", "SomeVam", "SOMEVAK", "SomeVak", ""       , "SomeVak"},
656         		new int[]    { 0        , 0        , 0        , 1        , 1        , 0       , 0        , 1        , 0        });
657     }
658 
659     /*
660      * A convenience method for performing document-searching operations involving range definitions. The array parameters must all be the same length,
661      * since this method will perform tests with the values given by entries located at the same indices.
662      * @param docType The document type to search for.
663      * @param principalId The ID of the user that will perform the search.
664      * @param fieldDefKey The name of the field given by the field definition on the searchable attribute.
665      * @param lowBounds The lower bounds to use in the tests; to ignore a lower bound for a test, use an empty String.
666      * @param upBounds The upper bounds to use in the tests; to ignore an upper bound for a test, use an empty String.
667      * @param resultSizes The expected number of documents to be returned by the search; use -1 to indicate that an exception should have occurred.
668      * @throws Exception
669      */
670     private void assertSearchBehavesAsExpected(DocumentType docType, String principalId, String fieldDefKey, String[] lowBounds, String[] upBounds,
671     		int[] resultSizes) throws Exception {
672         DocumentSearchCriteria.Builder criteria = null;
673         DocumentSearchResults results = null;
674         DocumentSearchService docSearchService = KEWServiceLocator.getDocumentSearchService();
675         for (int i = 0; i < resultSizes.length; i++) {
676         	criteria = DocumentSearchCriteria.Builder.create();
677         	criteria.setDocumentTypeName(docType.getName());
678             addSearchableAttributeRange(criteria, fieldDefKey, lowBounds[i], upBounds[i], true);
679         	try {
680         		results = docSearchService.lookupDocuments(principalId, criteria.build());
681         		if (resultSizes[i] < 0) {
682         			fail(fieldDefKey + "'s search at loop index " + i + " should have thrown an exception");
683         		}
684         		assertEquals(fieldDefKey
685                         + "'s search results at loop index "
686                         + i
687                         + " returned the wrong number of documents.", resultSizes[i], results.getSearchResults().size());
688         	} catch (Exception ex) {
689         		if (resultSizes[i] >= 0) {
690         			fail(fieldDefKey + "'s search at loop index " + i + " should not have thrown an exception");
691         		}
692         	}
693         }
694     }
695 }