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.Before;
19  import org.junit.Ignore;
20  import org.junit.Test;
21  import org.kuali.rice.core.api.uif.RemotableAttributeError;
22  import org.kuali.rice.core.framework.persistence.jdbc.sql.SQLUtils;
23  import org.kuali.rice.kew.api.KewApiConstants;
24  import org.kuali.rice.kew.api.WorkflowDocument;
25  import org.kuali.rice.kew.api.WorkflowDocumentFactory;
26  import org.kuali.rice.kew.api.document.attribute.WorkflowAttributeDefinition;
27  import org.kuali.rice.kew.api.document.search.DocumentSearchCriteria;
28  import org.kuali.rice.kew.api.document.search.DocumentSearchResults;
29  import org.kuali.rice.kew.api.extension.ExtensionDefinition;
30  import org.kuali.rice.kew.docsearch.DocumentSearchInternalUtils;
31  import org.kuali.rice.kew.docsearch.DocumentSearchTestBase;
32  import org.kuali.rice.kew.docsearch.TestXMLSearchableAttributeDateTime;
33  import org.kuali.rice.kew.docsearch.TestXMLSearchableAttributeFloat;
34  import org.kuali.rice.kew.docsearch.TestXMLSearchableAttributeLong;
35  import org.kuali.rice.kew.docsearch.TestXMLSearchableAttributeString;
36  import org.kuali.rice.kew.docsearch.service.DocumentSearchService;
37  import org.kuali.rice.kew.doctype.bo.DocumentType;
38  import org.kuali.rice.kew.doctype.service.DocumentTypeService;
39  import org.kuali.rice.kew.exception.WorkflowServiceErrorException;
40  import org.kuali.rice.kew.service.KEWServiceLocator;
41  import org.kuali.rice.kim.api.identity.Person;
42  import org.kuali.rice.kim.api.services.KimApiServiceLocator;
43  
44  import java.math.BigDecimal;
45  import java.util.Calendar;
46  import java.util.List;
47  
48  import static org.junit.Assert.*;
49  import static org.junit.Assert.assertEquals;
50  
51  /**
52   * Tests the StandardGenericXMLSearchableAttribute.
53   *
54   * KULWF-654: Tests the resolution to this issue by configuring a CustomActionListAttribute as well as a
55   * searchable attribute.
56   */
57  public class StandardGenericXMLSearchableAttributeRangesTest extends DocumentSearchTestBase {
58      private DocumentSearchService docSearchService;
59  
60      protected void loadTestData() throws Exception {
61          loadXmlFile("XmlConfig.xml");
62      }
63  
64      @Before
65      public void retrieveDocSearchSvc() {
66          docSearchService = (DocumentSearchService) KEWServiceLocator.getService(KEWServiceLocator.DOCUMENT_SEARCH_SERVICE);
67      }
68  
69      /*
70       * Test method for 'org.kuali.rice.kew.docsearch.xml.StandardGenericXMLSearchableAttribute.getSearchingRows()'
71       */
72      /*
73      @Ignore("See KULRICE-2988")
74      @Test public void testGetSearchingRowsUsingRangeSearches() {
75          StandardGenericXMLSearchableAttribute searchAttribute = getAttribute("XMLSearchableAttributeStringRange");
76          String documentTypeName = "SearchDocType";
77          DocumentSearchContext context = DocSearchUtils.getDocumentSearchContext("", documentTypeName, "");
78          List<Row> searchRows = searchAttribute.getSearchingRows(context);
79          if ((new SearchableAttributeStringValue()).allowsRangeSearches()) {
80          	fail("Cannot perform range search on string field at database level");
81          } else {
82              assertEquals("Invalid number of search rows", 1, searchRows.size());
83              Row row = searchRows.get(0);
84              assertEquals("Invalid number of fields for search row", 1, row.getFields().size());
85              assertFalse("Field is the member of a range when ranges are not allowed",((Field)row.getField(0)).isMemberOfRange());
86          }
87  
88          searchAttribute = getAttribute("XMLSearchableAttributeStdLongRange");
89          // search def :  rangeSearch=true
90          // range def  :
91          // upper def  :
92          // lower def  :
93          searchRows = searchAttribute.getSearchingRows(context);
94          if ((new SearchableAttributeLongValue()).allowsRangeSearches()) {
95              assertEquals("Invalid number of search rows", 2, searchRows.size());
96              for (int i = 1; i <= searchRows.size(); i++) {
97                  Row row = searchRows.get(i - 1);
98  	            assertEquals("Invalid number of fields for search row " + i, 1, row.getFields().size());
99  	            Field field = (Field)(row.getField(0));
100 	            assertTrue("Field should be the member of a range",field.isMemberOfRange());
101 	            assertTrue("Field should not be inclusive",field.isInclusive());
102 	            assertFalse("Field should not be using datepicker", field.isDatePicker());
103 			}
104         } else {
105             assertEquals("Invalid number of search rows", 1, searchRows.size());
106             Row row = searchRows.get(0);
107             assertEquals("Invalid number of fields for search row", 1, row.getFields().size());
108             Field field = (Field)(row.getField(0));
109             assertFalse("Field is the member of a range when ranges are not allowed",field.isMemberOfRange());
110             assertFalse("Field is inclusive when ranges are not allowed",field.isInclusive());
111             assertFalse("Field should not be using datepicker", field.isDatePicker());
112         }
113 
114         searchAttribute = getAttribute("XMLSearchableAttributeStdFloatRange");
115         // search def :
116         // range def  :  inclusive=false
117         // upper def  :  label=ending
118         // lower def  :  label=starting
119         searchRows = searchAttribute.getSearchingRows(context);
120         if ((new SearchableAttributeFloatValue()).allowsRangeSearches()) {
121             assertEquals("Invalid number of search rows", 2, searchRows.size());
122             for (int i = 1; i <= searchRows.size(); i++) {
123                 Row row = searchRows.get(i - 1);
124 	            assertEquals("Invalid number of fields for search row " + i, 1, row.getFields().size());
125 	            Field field = (Field)(row.getField(0));
126 	            assertTrue("Upper and Lower Fields should be members of a range",field.isMemberOfRange());
127 	            assertFalse("Upper and Lower Fields should not be inclusive",field.isInclusive());
128 	            String labelValue = null;
129 	            if (field.getPropertyName().startsWith(KewApiConstants.SearchableAttributeConstants.RANGE_LOWER_BOUND_PROPERTY_PREFIX)) {
130 	            	labelValue = "starting";
131 	            } else if (field.getPropertyName().startsWith(KewApiConstants.SearchableAttributeConstants.RANGE_UPPER_BOUND_PROPERTY_PREFIX)) {
132 	            	labelValue = "ending";
133 	            } else {
134 	            	fail("Field should have prefix consistent with upper or lower bound of a range");
135 	            }
136 	            assertEquals("Field label is incorrect.", labelValue, field.getFieldLabel());
137 	            assertFalse("Field should not be using datepicker", field.isDatePicker());
138 			}
139         } else {
140             assertEquals("Invalid number of search rows", 1, searchRows.size());
141             Row row = searchRows.get(0);
142             assertEquals("Invalid number of fields for search row", 1, row.getFields().size());
143             Field field = (Field)(row.getField(0));
144             assertFalse("Field is the member of a range when ranges are not allowed",field.isMemberOfRange());
145             assertFalse("Field should not be using datepicker", field.isDatePicker());
146         }
147 
148         searchAttribute = getAttribute("XMLSearchableAttributeStdDateTimeRange");
149         // search def :  datePicker=false
150         // range def  :  inclusive=false
151         // upper def  :  inclusvie=true - datePicker=true
152         // lower def  :
153         searchRows = searchAttribute.getSearchingRows(context);
154         if ((new SearchableAttributeDateTimeValue()).allowsRangeSearches()) {
155             assertEquals("Invalid number of search rows", 2, searchRows.size());
156             for (int i = 0; i < searchRows.size(); i++) {
157                 Row row = searchRows.get(i);
158 	            assertTrue("Invalid number of fields for search row", row.getFields().size() > 0);
159 	            Field field = (Field)(row.getField(0));
160 	            assertTrue("Field should be the member of a range search", field.isMemberOfRange());
161 	            if (field.getPropertyName().startsWith(KewApiConstants.SearchableAttributeConstants.RANGE_LOWER_BOUND_PROPERTY_PREFIX)) {
162 	            	// this is the lower bound row
163 	            	assertFalse("Upper Field should not be using datepicker field", field.isDatePicker());
164 	            	assertFalse("Upper Field should not be inclusive", field.isInclusive());
165 	            } else if (field.getPropertyName().startsWith(KewApiConstants.SearchableAttributeConstants.RANGE_UPPER_BOUND_PROPERTY_PREFIX)) {
166 	            	// this is the upper bound row
167 	            	assertTrue("Upper Field should be using datepicker field", field.isDatePicker());
168 	            	assertTrue("Upper Field should not be inclusive", field.isInclusive());
169 	            	assertEquals("Row should have two fields (including the datepicker field)", 2, row.getFields().size());
170 	            	assertEquals("Second field in row  should be of type datepicker", Field.DATEPICKER, row.getField(1).getFieldType());
171 	            } else {
172 	            	fail("Field should have prefix consistent with upper or lower bound of a range");
173 	            }
174 			}
175         } else {
176             assertEquals("Invalid number of search rows", 1, searchRows.size());
177             Row row = searchRows.get(0);
178             // check to make sure our datepicker field didn't make it to the search rows
179             assertEquals("Invalid number of fields", 1, row.getFields().size());
180             assertFalse("Field is the member of a range when ranges are not allowed",((Field)(row.getField(0))).isMemberOfRange());
181         }
182     }
183 
184     */
185     /*
186      * Test method for 'org.kuali.rice.kew.docsearch.xml.StandardGenericXMLSearchableAttribute.validateUserSearchInputs(Map)'
187      */
188     @Test  public void testValidateUserSearchRangeInputs() {
189     	// upper bound and lower bound fields should be using same validation... we just altername which formKey we use here
190         StandardGenericXMLSearchableAttribute searchAttribute = getAttribute("XMLSearchableAttributeStringRange");
191         ExtensionDefinition ed = createExtensionDefinition("XMLSearchableAttributeStringRange");
192 
193         String documentTypeName = "SearchDocType";
194 
195         assertDocumentSearchCriteriaValidation(searchAttribute, ed, TestXMLSearchableAttributeString.SEARCH_STORAGE_KEY, ">= jack", false);
196 
197         RemotableAttributeError error = assertDocumentSearchCriteriaValidation(searchAttribute, ed, TestXMLSearchableAttributeString.SEARCH_STORAGE_KEY, "<= jack.jack", true);
198         assertEquals("Validation error should match xml attribute message", "Invalid first name", error.getMessage());
199 
200         assertDocumentSearchCriteriaValidation(searchAttribute, ed, TestXMLSearchableAttributeString.SEARCH_STORAGE_KEY, ">= jack*jack", false); // TODO: * gets stripped from value
201 
202         searchAttribute = getAttribute("XMLSearchableAttributeStdLongRange");
203         ed = createExtensionDefinition("XMLSearchableAttributeStdLongRange");
204         assertDocumentSearchCriteriaValidation(searchAttribute, ed, TestXMLSearchableAttributeLong.SEARCH_STORAGE_KEY, "<= " + TestXMLSearchableAttributeLong.SEARCH_STORAGE_VALUE.toString(), false);
205         error = assertDocumentSearchCriteriaValidation(searchAttribute, ed, TestXMLSearchableAttributeLong.SEARCH_STORAGE_KEY, ">= " + TestXMLSearchableAttributeLong.SEARCH_STORAGE_VALUE.toString() + ".33", true);
206         assertTrue("Validation error is incorrect", error.getMessage().endsWith("does not conform to standard validation for field type."));
207         error = assertDocumentSearchCriteriaValidation(searchAttribute, ed, TestXMLSearchableAttributeLong.SEARCH_STORAGE_KEY, "<= jack*jack", true);
208         assertTrue("Validation error is incorrect", error.getMessage().endsWith("does not conform to standard validation for field type."));
209 
210         searchAttribute = getAttribute("XMLSearchableAttributeStdFloatRange");
211         ed = createExtensionDefinition("XMLSearchableAttributeStdFloatRange");
212         assertDocumentSearchCriteriaValidation(searchAttribute, ed, TestXMLSearchableAttributeFloat.SEARCH_STORAGE_KEY, ">= " + TestXMLSearchableAttributeFloat.SEARCH_STORAGE_VALUE.toString(), false);
213         error = assertDocumentSearchCriteriaValidation(searchAttribute, ed, TestXMLSearchableAttributeFloat.SEARCH_STORAGE_KEY, "<= " + TestXMLSearchableAttributeFloat.SEARCH_STORAGE_VALUE.toString() + "a", true);
214         assertTrue("Validation error is incorrect", error.getMessage().endsWith("does not conform to standard validation for field type."));
215         error = assertDocumentSearchCriteriaValidation(searchAttribute, ed, TestXMLSearchableAttributeFloat.SEARCH_STORAGE_KEY, ">= " + TestXMLSearchableAttributeFloat.SEARCH_STORAGE_VALUE.toString() + "*", true);
216         assertTrue("Validation error is incorrect", error.getMessage().endsWith("does not conform to standard validation for field type."));
217 
218         searchAttribute = getAttribute("XMLSearchableAttributeStdDateTimeRange");
219         ed = createExtensionDefinition("XMLSearchableAttributeStdDateTimeRange");
220         assertDocumentSearchCriteriaValidation(searchAttribute, ed, TestXMLSearchableAttributeDateTime.SEARCH_STORAGE_KEY, "<= " + DocumentSearchInternalUtils.getDisplayValueWithDateOnly(TestXMLSearchableAttributeDateTime.SEARCH_STORAGE_VALUE), false);
221         assertDocumentSearchCriteriaValidation(searchAttribute, ed, TestXMLSearchableAttributeDateTime.SEARCH_STORAGE_KEY, ">= 001/5/08", false);
222         error = assertDocumentSearchCriteriaValidation(searchAttribute, ed, TestXMLSearchableAttributeDateTime.SEARCH_STORAGE_KEY, ">= 41/5/08", true);
223         assertTrue("Validation error is incorrect", error.getMessage().endsWith("does not conform to standard validation for field type."));
224         error = assertDocumentSearchCriteriaValidation(searchAttribute, ed, TestXMLSearchableAttributeDateTime.SEARCH_STORAGE_KEY, "<= 01/02/20*", true);
225         assertTrue("Validation error is incorrect", error.getMessage().endsWith("does not conform to standard validation for field type."));
226     }
227 
228     /**
229      * Helper to assert document search criteria validation
230      */
231     protected RemotableAttributeError assertDocumentSearchCriteriaValidation(StandardGenericXMLSearchableAttribute attribute, ExtensionDefinition ed, String attrkey, String attrvalue, boolean expectError) {
232         DocumentSearchCriteria.Builder dscb = DocumentSearchCriteria.Builder.create();
233         dscb.addDocumentAttributeValue(attrkey, attrvalue);
234 
235         List<RemotableAttributeError> errors = attribute.validateDocumentAttributeCriteria(ed, dscb.build());
236 
237         if (expectError) {
238             assertEquals("Validation should return a single error message.", 1, errors.size());
239             return errors.get(0);
240         } else {
241             assertEquals("Validation should not have returned an error.", 0, errors.size());
242             return null;
243         }
244     }
245     
246     /**
247      * Creates an ExtensionDefinition for the specified attribute with XML config pulled from the db
248      */
249     protected static ExtensionDefinition createExtensionDefinition(String attrName) {
250         ExtensionDefinition.Builder edb = ExtensionDefinition.Builder.create(attrName, KewApiConstants.SEARCHABLE_XML_ATTRIBUTE_TYPE, StandardGenericXMLSearchableAttribute.class.getName());
251         edb.getConfiguration().put(KewApiConstants.ATTRIBUTE_XML_CONFIG_DATA, KEWServiceLocator.getRuleAttributeService().findByName(attrName).getXmlConfigData());
252         return edb.build();
253     }
254 
255     /**
256      * Sets up a doc for searching with ranged queries
257      */
258     protected WorkflowDocument setUpSearchableDoc() {
259         String documentTypeName = "SearchDocTypeRangeSearchDataType";
260     	DocumentType docType = ((DocumentTypeService)KEWServiceLocator.getService(KEWServiceLocator.DOCUMENT_TYPE_SERVICE)).findByName(documentTypeName);
261         String userNetworkId = "rkirkend";
262         WorkflowDocument workflowDocument = WorkflowDocumentFactory.createDocument(getPrincipalIdForName(userNetworkId), documentTypeName);
263 
264         /*
265          *   Below we are using the keys and values from the custom searchable attribute classes' static constants but
266          *   this is only for convenience as those should always be valid values to test for.
267          */
268         // adding string searchable attribute
269         WorkflowAttributeDefinition.Builder stringXMLDef = WorkflowAttributeDefinition.Builder.create("XMLSearchableAttributeStringRange");
270         stringXMLDef.addPropertyDefinition(TestXMLSearchableAttributeString.SEARCH_STORAGE_KEY, TestXMLSearchableAttributeString.SEARCH_STORAGE_VALUE);
271         workflowDocument.addSearchableDefinition(stringXMLDef.build());
272         // adding long searchable attribute
273         WorkflowAttributeDefinition.Builder longXMLDef = WorkflowAttributeDefinition.Builder.create("XMLSearchableAttributeStdLongRange");
274         longXMLDef.addPropertyDefinition(TestXMLSearchableAttributeLong.SEARCH_STORAGE_KEY, TestXMLSearchableAttributeLong.SEARCH_STORAGE_VALUE.toString());
275         workflowDocument.addSearchableDefinition(longXMLDef.build());
276         // adding float searchable attribute
277         WorkflowAttributeDefinition.Builder floatXMLDef = WorkflowAttributeDefinition.Builder.create("XMLSearchableAttributeStdFloatRange");
278         floatXMLDef.addPropertyDefinition(TestXMLSearchableAttributeFloat.SEARCH_STORAGE_KEY, TestXMLSearchableAttributeFloat.SEARCH_STORAGE_VALUE.toString());
279         workflowDocument.addSearchableDefinition(floatXMLDef.build());
280         // adding string searchable attribute
281         WorkflowAttributeDefinition.Builder dateXMLDef = WorkflowAttributeDefinition.Builder.create("XMLSearchableAttributeStdDateTimeRange");
282         dateXMLDef.addPropertyDefinition(TestXMLSearchableAttributeDateTime.SEARCH_STORAGE_KEY, DocumentSearchInternalUtils
283                 .getDisplayValueWithDateOnly(TestXMLSearchableAttributeDateTime.SEARCH_STORAGE_VALUE));
284         workflowDocument.addSearchableDefinition(dateXMLDef.build());
285 
286         workflowDocument.setTitle("Routing style");
287         workflowDocument.route("routing this document.");
288 
289         return WorkflowDocumentFactory.loadDocument(getPrincipalIdForName(userNetworkId), workflowDocument.getDocumentId());
290     }
291 
292     /**
293      * Special result which indicates a range validation error is expected
294      */
295     private static final int EXPECT_EXCEPTION = -1;
296 
297     /**
298      * Helper that asserts range search results
299      */
300     protected void assertRangeSearchResults(String docType, String userId, String attrKey, String lowerBound, String upperBound, boolean upperBoundInclusive, int expected) throws WorkflowServiceErrorException {
301         DocumentSearchCriteria.Builder criteria = DocumentSearchCriteria.Builder.create();
302         criteria.setDocumentTypeName(docType);
303 
304         addSearchableAttributeRange(criteria, attrKey, lowerBound, upperBound, upperBoundInclusive);
305 
306         DocumentSearchResults results;
307         try {
308             results = docSearchService.lookupDocuments(userId, criteria.build());
309             if (expected == EXPECT_EXCEPTION) fail("Error should have been thrown for invalid range");
310         } catch (WorkflowServiceErrorException e) {
311             if (expected == EXPECT_EXCEPTION) return;
312             else throw e;
313         }
314 
315         assertEquals("Search results should have " + expected + " document(s).", expected, results.getSearchResults().size());
316     }
317 
318     @Test public void testStringRanges() throws Exception {
319         WorkflowDocument doc = setUpSearchableDoc();
320         String userId = doc.getInitiatorPrincipalId();
321         String docType = doc.getDocumentTypeName();
322 
323         assertRangeSearchResults(docType, userId, TestXMLSearchableAttributeString.SEARCH_STORAGE_KEY, TestXMLSearchableAttributeString.SEARCH_STORAGE_VALUE, null, false, 1);
324     }
325 
326     @Test public void testLongRanges() throws Exception {
327         WorkflowDocument doc = setUpSearchableDoc();
328         String userId = doc.getInitiatorPrincipalId();
329         String docType = doc.getDocumentTypeName();
330 
331         String searchAttributeLongKey = TestXMLSearchableAttributeLong.SEARCH_STORAGE_KEY;
332         Long searchAttributeLongValue = TestXMLSearchableAttributeLong.SEARCH_STORAGE_VALUE.longValue();
333         Long longValueToUse = null;
334 
335         // test lower bound only
336         longValueToUse = searchAttributeLongValue; // lowerbound == value
337         assertRangeSearchResults(docType, userId, searchAttributeLongKey, longValueToUse.toString(), null, false, 1);
338 
339         longValueToUse = Long.valueOf(searchAttributeLongValue.longValue() - 1); // lowerbound below value
340         assertRangeSearchResults(docType, userId, searchAttributeLongKey, longValueToUse.toString(), null, false, 1);
341 
342         longValueToUse = Long.valueOf(searchAttributeLongValue.longValue() + 1); // lowerbound is above value
343         assertRangeSearchResults(docType, userId, searchAttributeLongKey, longValueToUse.toString(), null, false, 0);
344 
345         // test upper bound only
346         longValueToUse = searchAttributeLongValue; // upper bound == value
347         assertRangeSearchResults(docType, userId, searchAttributeLongKey, null, longValueToUse.toString(), true, 1);
348 
349         longValueToUse = Long.valueOf(searchAttributeLongValue.longValue() - 1); // upper bound < value
350         assertRangeSearchResults(docType, userId, searchAttributeLongKey, null, longValueToUse.toString(), true, 0);
351 
352         longValueToUse = Long.valueOf(searchAttributeLongValue.longValue() + 1); // upper bound > value
353         assertRangeSearchResults(docType, userId, searchAttributeLongKey, null, longValueToUse.toString(), true, 1);
354 
355         // test both bounds
356         // lowerbound == upperbound == value
357         assertRangeSearchResults(docType, userId, searchAttributeLongKey,
358                                  Long.valueOf(searchAttributeLongValue.longValue()).toString(),
359                                  Long.valueOf(searchAttributeLongValue.longValue()).toString(), true, 1);
360 
361         // lower and upper bound > value
362         assertRangeSearchResults(docType, userId, searchAttributeLongKey,
363                                  Long.valueOf(searchAttributeLongValue.longValue() + 2).toString(),
364                                  Long.valueOf(searchAttributeLongValue.longValue() + 4).toString(), true, 0);
365 
366         // lower and upper bound < value, but lower > upper
367         assertRangeSearchResults(docType, userId, searchAttributeLongKey,
368                                  Long.valueOf(searchAttributeLongValue.longValue() - 2).toString(),
369                                  Long.valueOf(searchAttributeLongValue.longValue() - 4).toString(), true, EXPECT_EXCEPTION);
370         // lower and upper bound < value, lower < upper
371         assertRangeSearchResults(docType, userId, searchAttributeLongKey,
372                                  Long.valueOf(searchAttributeLongValue.longValue() - 4).toString(),
373                                  Long.valueOf(searchAttributeLongValue.longValue() - 2).toString(), true, 0);
374 
375         // lower bound < value, upper bound > value
376         assertRangeSearchResults(docType, userId, searchAttributeLongKey,
377                                  Long.valueOf(searchAttributeLongValue.longValue() - 2).toString(),
378                                  Long.valueOf(searchAttributeLongValue.longValue() + 2).toString(), true, 1);
379 
380         // upper < lower
381         assertRangeSearchResults(docType, userId, searchAttributeLongKey,
382                                  Long.valueOf(searchAttributeLongValue.longValue() + 2).toString(),
383                                  Long.valueOf(searchAttributeLongValue.longValue() - 2).toString(), true, EXPECT_EXCEPTION);
384     }
385 
386     @Test public void testFloatRanges() throws Exception {
387         WorkflowDocument doc = setUpSearchableDoc();
388         String userId = doc.getInitiatorPrincipalId();
389         String docType = doc.getDocumentTypeName();
390 
391         String searchAttributeFloatKey = TestXMLSearchableAttributeFloat.SEARCH_STORAGE_KEY;
392         BigDecimal searchAttributeFloatValue = TestXMLSearchableAttributeFloat.SEARCH_STORAGE_VALUE;
393 
394         BigDecimal floatValueToUse = null;
395         // test lower bound only
396         floatValueToUse = searchAttributeFloatValue; // lower bound == value
397         // NOTE: original test asserted 0 results, mysql actually does match the value
398         assertRangeSearchResults(docType, userId, searchAttributeFloatKey, floatValueToUse.toString(), null, false, 1);
399 
400         floatValueToUse = searchAttributeFloatValue.subtract(BigDecimal.ONE); // lowerbound < value
401         assertRangeSearchResults(docType, userId, searchAttributeFloatKey, floatValueToUse.toString(), null, false, 1);
402 
403         floatValueToUse = searchAttributeFloatValue.add(BigDecimal.ONE); // lowerbound > value
404         assertRangeSearchResults(docType, userId, searchAttributeFloatKey, floatValueToUse.toString(), null, false, 0);
405 
406         // test upper bound only
407         floatValueToUse = searchAttributeFloatValue; // upperbound == value (does not match float)
408         // NOTE: another case where original test had 0 results, but in fact we see a float match
409         assertRangeSearchResults(docType, userId, searchAttributeFloatKey, null, floatValueToUse.toString(), true, 1);
410 
411         floatValueToUse = searchAttributeFloatValue.subtract(BigDecimal.ONE); // upperbound < value
412         assertRangeSearchResults(docType, userId, searchAttributeFloatKey, null, floatValueToUse.toString(), true, 0);
413 
414         floatValueToUse = searchAttributeFloatValue.add(BigDecimal.ONE); // upperbound > value
415         assertRangeSearchResults(docType, userId, searchAttributeFloatKey, null, floatValueToUse.toString(), true, 1);
416 
417         // test both bounds
418         // upper == lower == value
419         // NOTE: original case had 0 results, now seeing 1 result
420         // search generator invokes criteria which calls addNumericRangeCriteria when produces: (EXT1.VAL BETWEEN 123456.3456 AND 123456.3456)
421         assertRangeSearchResults(docType, userId, searchAttributeFloatKey, searchAttributeFloatValue.toString(), searchAttributeFloatValue.toString(), true, 1);
422 
423         // upper and lower > value
424         assertRangeSearchResults(docType, userId, searchAttributeFloatKey,
425                                  searchAttributeFloatValue.add(new BigDecimal(2)).toString(),
426                                  searchAttributeFloatValue.add(new BigDecimal(4)).toString(), true, 0);
427 
428         // upper and lower < value
429         assertRangeSearchResults(docType, userId, searchAttributeFloatKey,
430                                  searchAttributeFloatValue.subtract(new BigDecimal(4)).toString(),
431                                  searchAttributeFloatValue.subtract(new BigDecimal(2)).toString(), true, 0);
432 
433         // lower < value, upper > value
434         assertRangeSearchResults(docType, userId, searchAttributeFloatKey,
435                                  searchAttributeFloatValue.subtract(new BigDecimal(2)).toString(),
436                                  searchAttributeFloatValue.add(new BigDecimal(2)).toString(), true, 1);
437 
438         // upper < lower
439         assertRangeSearchResults(docType, userId, searchAttributeFloatKey,
440                                  searchAttributeFloatValue.add(new BigDecimal(2)).toString(),
441                                  searchAttributeFloatValue.subtract(new BigDecimal(2)).toString(), true, EXPECT_EXCEPTION);
442     }
443 
444     @Test public void testDateRanges() throws Exception {
445         WorkflowDocument doc = setUpSearchableDoc();
446         String userId = doc.getInitiatorPrincipalId();
447         String docType = doc.getDocumentTypeName();
448 
449         // begin datetime attribute value testing
450         // inclusive = ?
451         String searchAttributeDateTimeKey = TestXMLSearchableAttributeDateTime.SEARCH_STORAGE_KEY;
452         Calendar searchAttributeDateTimeValue = TestXMLSearchableAttributeDateTime.SEARCH_STORAGE_VALUE.toGregorianCalendar();
453 
454         Calendar calendarValueToUse = null;
455         // test lower bound only
456         calendarValueToUse = (Calendar) searchAttributeDateTimeValue.clone(); // lower == value
457         String valueToSearch = DocumentSearchInternalUtils.getDisplayValueWithDateOnly(SQLUtils.convertCalendar(calendarValueToUse));
458         // NOTE: matches now
459         assertRangeSearchResults(docType, userId, searchAttributeDateTimeKey, valueToSearch, null, false, 1);
460 
461         calendarValueToUse = (Calendar) searchAttributeDateTimeValue.clone();
462         calendarValueToUse.add(Calendar.DATE, -1); // lower < value
463         valueToSearch = DocumentSearchInternalUtils.getDisplayValueWithDateOnly(SQLUtils.convertCalendar(calendarValueToUse));
464         assertRangeSearchResults(docType, userId, searchAttributeDateTimeKey, valueToSearch, null, false, 1);
465 
466         calendarValueToUse = (Calendar) searchAttributeDateTimeValue.clone();
467         calendarValueToUse.add(Calendar.DATE, 1); // lower > value
468         valueToSearch = DocumentSearchInternalUtils.getDisplayValueWithDateOnly(SQLUtils.convertCalendar(calendarValueToUse));
469         assertRangeSearchResults(docType, userId, searchAttributeDateTimeKey, valueToSearch, null, false, 0);
470 
471         // test upper bound only
472         calendarValueToUse = (Calendar) searchAttributeDateTimeValue.clone(); // upper == value (inclusivity true)
473         assertRangeSearchResults(docType, userId, searchAttributeDateTimeKey,
474                                  null,
475                                  DocumentSearchInternalUtils.getDisplayValueWithDateOnly(SQLUtils.convertCalendar(calendarValueToUse)), true, 1);
476 
477         calendarValueToUse = (Calendar) searchAttributeDateTimeValue.clone();
478         calendarValueToUse.add(Calendar.DATE, -1); // upper < value
479         assertRangeSearchResults(docType, userId, searchAttributeDateTimeKey,
480                                  null,
481                                  DocumentSearchInternalUtils.getDisplayValueWithDateOnly(SQLUtils.convertCalendar(calendarValueToUse)), true, 0);
482 
483         calendarValueToUse = (Calendar) searchAttributeDateTimeValue.clone();
484         calendarValueToUse.add(Calendar.DATE, 1); // upper > value
485         assertRangeSearchResults(docType, userId, searchAttributeDateTimeKey,
486                                  null,
487                                  DocumentSearchInternalUtils.getDisplayValueWithDateOnly(SQLUtils.convertCalendar(calendarValueToUse)), true, 1);
488 
489         // test both bounds
490         Calendar lowerBoundValue = (Calendar) searchAttributeDateTimeValue.clone();
491         Calendar upperBoundValue = (Calendar) searchAttributeDateTimeValue.clone(); // upper == lower == value (inclusivity true)
492         // NOTE: matches now
493         assertRangeSearchResults(docType, userId, searchAttributeDateTimeKey,
494                                  DocumentSearchInternalUtils.getDisplayValueWithDateOnly(SQLUtils.convertCalendar(lowerBoundValue)),
495                                  DocumentSearchInternalUtils.getDisplayValueWithDateOnly(SQLUtils.convertCalendar(upperBoundValue)), true, 1);
496 
497         lowerBoundValue = (Calendar) searchAttributeDateTimeValue.clone();
498         lowerBoundValue.add(Calendar.DATE, 2);
499         upperBoundValue = (Calendar) searchAttributeDateTimeValue.clone();
500         upperBoundValue.add(Calendar.DATE, 4);  // upper and lower > value
501         assertRangeSearchResults(docType, userId, searchAttributeDateTimeKey,
502                                  DocumentSearchInternalUtils.getDisplayValueWithDateOnly(SQLUtils.convertCalendar(lowerBoundValue)),
503                                  DocumentSearchInternalUtils.getDisplayValueWithDateOnly(SQLUtils.convertCalendar(upperBoundValue)), true, 0);
504 
505         lowerBoundValue = (Calendar) searchAttributeDateTimeValue.clone();
506         lowerBoundValue.add(Calendar.DATE, -4);
507         upperBoundValue = (Calendar) searchAttributeDateTimeValue.clone();
508         upperBoundValue.add(Calendar.DATE, -2); // upper and lower < value
509         assertRangeSearchResults(docType, userId, searchAttributeDateTimeKey,
510                                  DocumentSearchInternalUtils.getDisplayValueWithDateOnly(SQLUtils.convertCalendar(lowerBoundValue)),
511                                  DocumentSearchInternalUtils.getDisplayValueWithDateOnly(SQLUtils.convertCalendar(upperBoundValue)), true, 0);
512 
513         lowerBoundValue = (Calendar) searchAttributeDateTimeValue.clone();
514         lowerBoundValue.add(Calendar.DATE, -2);
515         upperBoundValue = (Calendar) searchAttributeDateTimeValue.clone();
516         upperBoundValue.add(Calendar.DATE, 2);  // lower < value, upper > value
517         assertRangeSearchResults(docType, userId, searchAttributeDateTimeKey,
518                                  DocumentSearchInternalUtils.getDisplayValueWithDateOnly(SQLUtils.convertCalendar(lowerBoundValue)),
519                                  DocumentSearchInternalUtils.getDisplayValueWithDateOnly(SQLUtils.convertCalendar(upperBoundValue)), true, 1);
520 
521         lowerBoundValue = (Calendar) searchAttributeDateTimeValue.clone();
522         lowerBoundValue.add(Calendar.DATE, 2);
523         upperBoundValue = (Calendar) searchAttributeDateTimeValue.clone();
524         upperBoundValue.add(Calendar.DATE, -2); // lower > upper == error
525         assertRangeSearchResults(docType, userId, searchAttributeDateTimeKey,
526                                  DocumentSearchInternalUtils.getDisplayValueWithDateOnly(SQLUtils.convertCalendar(lowerBoundValue)),
527                                  DocumentSearchInternalUtils.getDisplayValueWithDateOnly(SQLUtils.convertCalendar(upperBoundValue)), true, EXPECT_EXCEPTION);
528     }
529 
530     /**
531      * Test searching by searchable attributes that use ranges
532      */
533     @Ignore // TODO: KULRICE-5630 delete this old test after search behavior in test*Ranges tests above has been verified/approved
534     @Test public void testSearchableAttributeRanges() throws Exception {
535         String documentTypeName = "SearchDocTypeRangeSearchDataType";
536     	DocumentType docType = ((DocumentTypeService)KEWServiceLocator.getService(KEWServiceLocator.DOCUMENT_TYPE_SERVICE)).findByName(documentTypeName);
537         String userNetworkId = "rkirkend";
538         WorkflowDocument workflowDocument = WorkflowDocumentFactory.createDocument(getPrincipalIdForName(userNetworkId), documentTypeName);
539 
540         /*
541          *   Below we are using the keys and values from the custom searchable attribute classes' static constants but
542          *   this is only for convenience as those should always be valid values to test for.
543          */
544         // adding string searchable attribute
545         WorkflowAttributeDefinition.Builder stringXMLDef = WorkflowAttributeDefinition.Builder.create("XMLSearchableAttributeStringRange");
546         stringXMLDef.addPropertyDefinition(TestXMLSearchableAttributeString.SEARCH_STORAGE_KEY, TestXMLSearchableAttributeString.SEARCH_STORAGE_VALUE);
547         workflowDocument.addSearchableDefinition(stringXMLDef.build());
548         // adding long searchable attribute
549         WorkflowAttributeDefinition.Builder longXMLDef = WorkflowAttributeDefinition.Builder.create("XMLSearchableAttributeStdLongRange");
550         longXMLDef.addPropertyDefinition(TestXMLSearchableAttributeLong.SEARCH_STORAGE_KEY, TestXMLSearchableAttributeLong.SEARCH_STORAGE_VALUE.toString());
551         workflowDocument.addSearchableDefinition(longXMLDef.build());
552         // adding float searchable attribute
553         WorkflowAttributeDefinition.Builder floatXMLDef = WorkflowAttributeDefinition.Builder.create("XMLSearchableAttributeStdFloatRange");
554         floatXMLDef.addPropertyDefinition(TestXMLSearchableAttributeFloat.SEARCH_STORAGE_KEY, TestXMLSearchableAttributeFloat.SEARCH_STORAGE_VALUE.toString());
555         workflowDocument.addSearchableDefinition(floatXMLDef.build());
556         // adding string searchable attribute
557         WorkflowAttributeDefinition.Builder dateXMLDef = WorkflowAttributeDefinition.Builder.create("XMLSearchableAttributeStdDateTimeRange");
558         dateXMLDef.addPropertyDefinition(TestXMLSearchableAttributeDateTime.SEARCH_STORAGE_KEY, DocumentSearchInternalUtils
559                 .getDisplayValueWithDateOnly(TestXMLSearchableAttributeDateTime.SEARCH_STORAGE_VALUE));
560         workflowDocument.addSearchableDefinition(dateXMLDef.build());
561 
562         workflowDocument.setTitle("Routing style");
563         workflowDocument.route("routing this document.");
564 
565         workflowDocument = WorkflowDocumentFactory.loadDocument(getPrincipalIdForName(userNetworkId), workflowDocument.getDocumentId());
566 
567         DocumentSearchService docSearchService = (DocumentSearchService) KEWServiceLocator.getService(KEWServiceLocator.DOCUMENT_SEARCH_SERVICE);
568         Person user = KimApiServiceLocator.getPersonService().getPersonByPrincipalName(userNetworkId);
569 
570         // begin string attribute value testing
571         DocumentSearchCriteria.Builder criteria = null;
572         DocumentSearchResults results = null;
573 
574         criteria = DocumentSearchCriteria.Builder.create();
575         criteria.setDocumentTypeName(documentTypeName);
576         addSearchableAttributeRange(criteria, TestXMLSearchableAttributeString.SEARCH_STORAGE_KEY, TestXMLSearchableAttributeString.SEARCH_STORAGE_VALUE, null, false);
577         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
578         int size = results.getSearchResults().size();
579         assertTrue("Searching for a lower bound of 'jack'. case insensitive, inclusive.  so searching for something >= 'JACK'. Should Return 1, but got" + size, 1 == size);
580 
581         // begin long attribute value testing
582         // inclusive = true
583         String searchAttributeLongKey = TestXMLSearchableAttributeLong.SEARCH_STORAGE_KEY;
584         Long searchAttributeLongValue = TestXMLSearchableAttributeLong.SEARCH_STORAGE_VALUE.longValue();
585         Long longValueToUse = null;
586 
587         // test lower bound only
588         criteria = DocumentSearchCriteria.Builder.create();
589         criteria.setDocumentTypeName(documentTypeName);
590         longValueToUse = searchAttributeLongValue; // lowerbound == value
591         addSearchableAttributeRange(criteria, searchAttributeLongKey, longValueToUse.toString(), null, false);
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         longValueToUse = Long.valueOf(searchAttributeLongValue.longValue() - 1); // lowerbound below value
598         addSearchableAttributeRange(criteria, searchAttributeLongKey, longValueToUse.toString(), null, false);
599         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
600         assertEquals("Search results should have one document.", 1, results.getSearchResults().size());
601 
602         criteria = DocumentSearchCriteria.Builder.create();
603         criteria.setDocumentTypeName(documentTypeName);
604         longValueToUse = Long.valueOf(searchAttributeLongValue.longValue() + 1); // lowerbound is above value
605         addSearchableAttributeRange(criteria, searchAttributeLongKey, longValueToUse.toString(), null, false);
606         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
607         assertEquals("Search results should have zero documents.", 0, results.getSearchResults().size());
608 
609         // test upper bound only
610         criteria = DocumentSearchCriteria.Builder.create();
611         criteria.setDocumentTypeName(documentTypeName);
612         longValueToUse = searchAttributeLongValue; // upper bound == value
613         addSearchableAttributeRange(criteria, searchAttributeLongKey, null, longValueToUse.toString(), true);
614         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
615         assertEquals("Search results should have one document.", 1, results.getSearchResults().size());
616 
617         criteria = DocumentSearchCriteria.Builder.create();
618         criteria.setDocumentTypeName(documentTypeName); // upper bound < value
619         longValueToUse = Long.valueOf(searchAttributeLongValue.longValue() - 1);
620         addSearchableAttributeRange(criteria, searchAttributeLongKey, null, longValueToUse.toString(), true);
621         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
622         assertEquals("Search results should have zero documents.", 0, results.getSearchResults().size());
623 
624         criteria = DocumentSearchCriteria.Builder.create();
625         criteria.setDocumentTypeName(documentTypeName); // upper bound > value
626         longValueToUse = Long.valueOf(searchAttributeLongValue.longValue() + 1);
627         addSearchableAttributeRange(criteria, searchAttributeLongKey, null, longValueToUse.toString(), true);
628         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
629         assertEquals("Search results should have one document.", 1, results.getSearchResults().size());
630 
631         // test both bounds
632         criteria = DocumentSearchCriteria.Builder.create();
633         criteria.setDocumentTypeName(documentTypeName);
634         // lowerbound == upperbound == value
635         addSearchableAttributeRange(criteria, searchAttributeLongKey, Long.valueOf(searchAttributeLongValue.longValue())
636                 .toString(), Long.valueOf(searchAttributeLongValue.longValue()).toString(), true);
637         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
638         assertEquals("Search results should have zero documents.", 1, results.getSearchResults().size());
639 
640         criteria = DocumentSearchCriteria.Builder.create();
641         criteria.setDocumentTypeName(documentTypeName);
642         // lower and upper bound > value
643         addSearchableAttributeRange(criteria, searchAttributeLongKey, Long.valueOf(
644                 searchAttributeLongValue.longValue() + 2).toString(), Long.valueOf(
645                 searchAttributeLongValue.longValue() + 4).toString(), true);
646         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
647         assertEquals("Search results should have zero documents.", 0, results.getSearchResults().size());
648 
649         criteria = DocumentSearchCriteria.Builder.create();
650         criteria.setDocumentTypeName(documentTypeName);
651         // lower and upper bound < value
652         addSearchableAttributeRange(criteria, searchAttributeLongKey, Long.valueOf(
653                 searchAttributeLongValue.longValue() - 2).toString(), Long.valueOf(
654                 searchAttributeLongValue.longValue() - 4).toString(), true);
655         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
656         assertEquals("Search results should have zero documents.", 0, results.getSearchResults().size());
657 
658         criteria = DocumentSearchCriteria.Builder.create();
659         criteria.setDocumentTypeName(documentTypeName);
660         // lower bound < value, upper bound > value
661         addSearchableAttributeRange(criteria, searchAttributeLongKey, Long.valueOf(
662                 searchAttributeLongValue.longValue() - 2).toString(), Long.valueOf(
663                 searchAttributeLongValue.longValue() + 2).toString(), true);
664         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
665         assertEquals("Search results should have one document.", 1, results.getSearchResults().size());
666 
667         criteria = DocumentSearchCriteria.Builder.create();
668         criteria.setDocumentTypeName(documentTypeName);
669         // upper < lower
670         addSearchableAttributeRange(criteria, searchAttributeLongKey, Long.valueOf(searchAttributeLongValue.longValue() + 2).toString(), Long.valueOf(searchAttributeLongValue.longValue() - 2).toString(), true);
671         try {
672             // KULRICE-5630 fails because SGXSearchableAttribute does detect ranges correctly yet
673             docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
674             //fail("Error should have been thrown for invalid range");
675         } catch (WorkflowServiceErrorException e) {}
676 
677         // begin float attribute value testing
678         String searchAttributeFloatKey = TestXMLSearchableAttributeFloat.SEARCH_STORAGE_KEY;
679         BigDecimal searchAttributeFloatValue = TestXMLSearchableAttributeFloat.SEARCH_STORAGE_VALUE;
680 
681         BigDecimal floatValueToUse = null;
682         // test lower bound only
683         criteria = DocumentSearchCriteria.Builder.create();
684         criteria.setDocumentTypeName(documentTypeName);
685         floatValueToUse = searchAttributeFloatValue; // lower bound == value (does not match float)
686         addSearchableAttributeRange(criteria, searchAttributeFloatKey, floatValueToUse.toString(), null, false);
687         // FIXME: KULRICE-5630 SGXSA does not interpret >= correctly, compares null lower to upper bound and fails validation of the range
688         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
689         assertEquals("Search results should have zero documents.", 0, results.getSearchResults().size());
690 
691         criteria = DocumentSearchCriteria.Builder.create();
692         criteria.setDocumentTypeName(documentTypeName);
693         floatValueToUse = searchAttributeFloatValue.subtract(BigDecimal.ONE); // lowerbound < value
694         addSearchableAttributeRange(criteria, searchAttributeFloatKey, floatValueToUse.toString(), null, false);
695         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
696         assertEquals("Search results should have one document.", 1, results.getSearchResults().size());
697 
698         criteria = DocumentSearchCriteria.Builder.create();
699         criteria.setDocumentTypeName(documentTypeName);
700         floatValueToUse = searchAttributeFloatValue.add(BigDecimal.ONE); // lowerbound > value
701         addSearchableAttributeRange(criteria, searchAttributeFloatKey, floatValueToUse.toString(), null, false);
702         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
703         assertEquals("Search results should have zero documents.", 0, results.getSearchResults().size());
704 
705         // test upper bound only
706         criteria = DocumentSearchCriteria.Builder.create();
707         criteria.setDocumentTypeName(documentTypeName);
708         floatValueToUse = searchAttributeFloatValue; // upperbound == value (does not match float)
709         addSearchableAttributeRange(criteria, searchAttributeFloatKey, null, floatValueToUse.toString(), true);
710         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
711         assertEquals("Search results should have zero documents.", 0, results.getSearchResults().size());
712 
713         criteria = DocumentSearchCriteria.Builder.create();
714         criteria.setDocumentTypeName(documentTypeName);
715         floatValueToUse = searchAttributeFloatValue.subtract(BigDecimal.ONE); // upperbound < value
716         addSearchableAttributeRange(criteria, searchAttributeFloatKey, null, floatValueToUse.toString(), true);
717         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
718         assertEquals("Search results should have zero documents.", 0, results.getSearchResults().size());
719 
720         criteria = DocumentSearchCriteria.Builder.create();
721         criteria.setDocumentTypeName(documentTypeName);
722         floatValueToUse = searchAttributeFloatValue.add(BigDecimal.ONE); // upperbound > value
723         addSearchableAttributeRange(criteria, searchAttributeFloatKey, null, floatValueToUse.toString(), true);
724         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
725         assertEquals("Search results should have one document.", 1, results.getSearchResults().size());
726 
727         // test both bounds
728         criteria = DocumentSearchCriteria.Builder.create();
729         criteria.setDocumentTypeName(documentTypeName); // upper == lower == value
730         addSearchableAttributeRange(criteria, searchAttributeFloatKey, searchAttributeFloatValue.toString(),
731                 searchAttributeFloatValue.toString(), true);
732         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
733         assertEquals("Search results should have zero documents.", 0, results.getSearchResults().size());
734 
735         criteria = DocumentSearchCriteria.Builder.create();
736         criteria.setDocumentTypeName(documentTypeName);
737         // upper and lower > value
738         addSearchableAttributeRange(criteria, searchAttributeFloatKey, (searchAttributeFloatValue.add(new BigDecimal(
739                 2))).toString(), (searchAttributeFloatValue.add(new BigDecimal(4))).toString(), true);
740         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
741         assertEquals("Search results should have zero documents.", 0, results.getSearchResults().size());
742 
743         criteria = DocumentSearchCriteria.Builder.create();
744         criteria.setDocumentTypeName(documentTypeName);
745         // upper and lower < value
746         addSearchableAttributeRange(criteria, searchAttributeFloatKey, (searchAttributeFloatValue.subtract(
747                 new BigDecimal(4))).toString(), (searchAttributeFloatValue.subtract(new BigDecimal(2))).toString(),
748                 true);
749         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
750         assertEquals("Search results should have zero documents.", 0, results.getSearchResults().size());
751 
752         criteria = DocumentSearchCriteria.Builder.create();
753         criteria.setDocumentTypeName(documentTypeName);
754         // lower < value, upper > value
755         addSearchableAttributeRange(criteria, searchAttributeFloatKey, (searchAttributeFloatValue.subtract(new BigDecimal(2))).toString(), (searchAttributeFloatValue.add(new BigDecimal(2))).toString(), true);
756         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
757         assertEquals("Search results should have one document.", 1, results.getSearchResults().size());
758 
759         criteria = DocumentSearchCriteria.Builder.create();
760         criteria.setDocumentTypeName(documentTypeName);
761         // upper < lower
762         addSearchableAttributeRange(criteria, searchAttributeFloatKey, (searchAttributeFloatValue.add(new BigDecimal(
763                 2))).toString(), (searchAttributeFloatValue.subtract(new BigDecimal(2))).toString(), true);
764         try {
765             docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
766             //fail("Error should have been thrown for invalid range");
767         } catch (WorkflowServiceErrorException e) {}
768 
769         // begin datetime attribute value testing
770         // inclusive = ?
771         String searchAttributeDateTimeKey = TestXMLSearchableAttributeDateTime.SEARCH_STORAGE_KEY;
772         Calendar searchAttributeDateTimeValue = TestXMLSearchableAttributeDateTime.SEARCH_STORAGE_VALUE.toGregorianCalendar();
773 
774         Calendar calendarValueToUse = null;
775         // test lower bound only
776         criteria = DocumentSearchCriteria.Builder.create();
777         criteria.setDocumentTypeName(documentTypeName);
778         calendarValueToUse = (Calendar) searchAttributeDateTimeValue.clone();
779         String valueToSearch = DocumentSearchInternalUtils.getDisplayValueWithDateOnly(SQLUtils.convertCalendar(
780                 calendarValueToUse));
781         // lower == value (TODO: no match? because inclusivity false?)
782         addSearchableAttributeRange(criteria, searchAttributeDateTimeKey, valueToSearch, null, false);
783         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
784         assertEquals("Search results should have zero documents.", 0, results.getSearchResults().size());
785 
786         criteria = DocumentSearchCriteria.Builder.create();
787         criteria.setDocumentTypeName(documentTypeName);
788         calendarValueToUse = (Calendar) searchAttributeDateTimeValue.clone();
789         calendarValueToUse.add(Calendar.DATE, -1); // lower < value
790         valueToSearch = DocumentSearchInternalUtils.getDisplayValueWithDateOnly(SQLUtils.convertCalendar(
791                 calendarValueToUse));
792         addSearchableAttributeRange(criteria, searchAttributeDateTimeKey, valueToSearch, null, false);
793         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
794         assertEquals("Search results should have one document.", 1, results.getSearchResults().size());
795 
796         criteria = DocumentSearchCriteria.Builder.create();
797         criteria.setDocumentTypeName(documentTypeName);
798         calendarValueToUse = (Calendar) searchAttributeDateTimeValue.clone();
799         calendarValueToUse.add(Calendar.DATE, 1); // lower > value
800         valueToSearch = DocumentSearchInternalUtils.getDisplayValueWithDateOnly(SQLUtils.convertCalendar(
801                 calendarValueToUse));
802         addSearchableAttributeRange(criteria, searchAttributeDateTimeKey, valueToSearch, null, false);
803         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
804         assertEquals("Search results should have zero documents.", 0, results.getSearchResults().size());
805 
806         // test upper bound only
807         criteria = DocumentSearchCriteria.Builder.create();
808         criteria.setDocumentTypeName(documentTypeName);
809         calendarValueToUse = (Calendar) searchAttributeDateTimeValue.clone(); // upper == value (inclusivity true)
810         addSearchableAttributeRange(criteria, searchAttributeDateTimeKey, null,
811                 DocumentSearchInternalUtils.getDisplayValueWithDateOnly(SQLUtils.convertCalendar(calendarValueToUse)), true);
812         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
813         assertEquals("Search results should have one document.", 1, results.getSearchResults().size());
814 
815         criteria = DocumentSearchCriteria.Builder.create();
816         criteria.setDocumentTypeName(documentTypeName);
817         calendarValueToUse = (Calendar) searchAttributeDateTimeValue.clone();
818         calendarValueToUse.add(Calendar.DATE, -1); // upper < value
819         addSearchableAttributeRange(criteria, searchAttributeDateTimeKey, null, DocumentSearchInternalUtils
820                 .getDisplayValueWithDateOnly(SQLUtils.convertCalendar(calendarValueToUse)), true);
821         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
822         assertEquals("Search results should have zero documents.", 0, results.getSearchResults().size());
823 
824         criteria = DocumentSearchCriteria.Builder.create();
825         criteria.setDocumentTypeName(documentTypeName);
826         calendarValueToUse = (Calendar) searchAttributeDateTimeValue.clone();
827         calendarValueToUse.add(Calendar.DATE, 1); // upper > value
828         addSearchableAttributeRange(criteria, searchAttributeDateTimeKey, null,
829                 DocumentSearchInternalUtils.getDisplayValueWithDateOnly(SQLUtils.convertCalendar(calendarValueToUse)), true);
830         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
831         assertEquals("Search results should have one document.", 1, results.getSearchResults().size());
832 
833         // test both bounds
834         criteria = DocumentSearchCriteria.Builder.create();
835         criteria.setDocumentTypeName(documentTypeName);
836         Calendar lowerBoundValue = (Calendar) searchAttributeDateTimeValue.clone();
837         Calendar upperBoundValue = (Calendar) searchAttributeDateTimeValue.clone(); // upper == lower == value (inclusivity true)
838         addSearchableAttributeRange(criteria, searchAttributeDateTimeKey, DocumentSearchInternalUtils
839                 .getDisplayValueWithDateOnly(SQLUtils.convertCalendar(lowerBoundValue)), DocumentSearchInternalUtils
840                 .getDisplayValueWithDateOnly(SQLUtils.convertCalendar(upperBoundValue)), true);
841         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
842         assertEquals("Search results should have zer documents.", 0, results.getSearchResults().size());
843 
844         criteria = DocumentSearchCriteria.Builder.create();
845         criteria.setDocumentTypeName(documentTypeName);
846         lowerBoundValue = (Calendar) searchAttributeDateTimeValue.clone();
847         lowerBoundValue.add(Calendar.DATE, 2);
848         upperBoundValue = (Calendar) searchAttributeDateTimeValue.clone();
849         upperBoundValue.add(Calendar.DATE, 4);  // upper and lower > value
850         addSearchableAttributeRange(criteria, searchAttributeDateTimeKey, DocumentSearchInternalUtils
851                 .getDisplayValueWithDateOnly(SQLUtils.convertCalendar(lowerBoundValue)), DocumentSearchInternalUtils
852                 .getDisplayValueWithDateOnly(SQLUtils.convertCalendar(upperBoundValue)), true);
853         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
854         assertEquals("Search results should have zero documents.", 0, results.getSearchResults().size());
855 
856         criteria = DocumentSearchCriteria.Builder.create();
857         criteria.setDocumentTypeName(documentTypeName);
858         lowerBoundValue = (Calendar) searchAttributeDateTimeValue.clone();
859         lowerBoundValue.add(Calendar.DATE, -4);
860         upperBoundValue = (Calendar) searchAttributeDateTimeValue.clone();
861         upperBoundValue.add(Calendar.DATE, -2); // upper and lower < value
862         addSearchableAttributeRange(criteria, searchAttributeDateTimeKey, DocumentSearchInternalUtils
863                 .getDisplayValueWithDateOnly(SQLUtils.convertCalendar(lowerBoundValue)), DocumentSearchInternalUtils
864                 .getDisplayValueWithDateOnly(SQLUtils.convertCalendar(upperBoundValue)), true);
865         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
866         assertEquals("Search results should have zero documents.", 0, results.getSearchResults().size());
867 
868         criteria = DocumentSearchCriteria.Builder.create();
869         criteria.setDocumentTypeName(documentTypeName);
870         lowerBoundValue = (Calendar) searchAttributeDateTimeValue.clone();
871         lowerBoundValue.add(Calendar.DATE, -2);
872         upperBoundValue = (Calendar) searchAttributeDateTimeValue.clone();
873         upperBoundValue.add(Calendar.DATE, 2);  // lower < value, upper > value
874         addSearchableAttributeRange(criteria, searchAttributeDateTimeKey, DocumentSearchInternalUtils
875                 .getDisplayValueWithDateOnly(SQLUtils.convertCalendar(lowerBoundValue)), DocumentSearchInternalUtils
876                 .getDisplayValueWithDateOnly(SQLUtils.convertCalendar(upperBoundValue)), true);
877         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
878         assertEquals("Search results should have one document.", 1, results.getSearchResults().size());
879 
880         criteria = DocumentSearchCriteria.Builder.create();
881         criteria.setDocumentTypeName(documentTypeName);
882         lowerBoundValue = (Calendar) searchAttributeDateTimeValue.clone();
883         lowerBoundValue.add(Calendar.DATE, 2);
884         upperBoundValue = (Calendar) searchAttributeDateTimeValue.clone();
885         upperBoundValue.add(Calendar.DATE, -2); // lower > upper == error
886         addSearchableAttributeRange(criteria, searchAttributeDateTimeKey, DocumentSearchInternalUtils
887                 .getDisplayValueWithDateOnly(SQLUtils.convertCalendar(lowerBoundValue)), DocumentSearchInternalUtils
888                 .getDisplayValueWithDateOnly(SQLUtils.convertCalendar(upperBoundValue)), true);
889         try {
890             docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
891             //fail("Error should have been thrown for invalid range");
892         } catch (WorkflowServiceErrorException e) {}
893     }
894 
895     /*
896      * Tests the XML string attributes on range definitions, using a technique similar to that of the testSearchableAttributeRanges() unit test.
897      */
898     @Test public void testRangeDefinitionStringAttributes() throws Exception {
899         String documentTypeName = "RangeDefinitionTestDocType";
900     	DocumentType docType = KEWServiceLocator.getDocumentTypeService().findByName(documentTypeName);
901         String principalName = "rkirkend";
902         String principalId = KimApiServiceLocator.getPersonService().getPersonByPrincipalName(principalName).getPrincipalId();
903         WorkflowDocument workflowDocument = WorkflowDocumentFactory.createDocument(principalId, documentTypeName);
904 
905         // adding inclusive-lower-bound searchable attribute
906         WorkflowAttributeDefinition.Builder inclusiveLowerXMLDef = WorkflowAttributeDefinition.Builder.create("TextFieldWithInclusiveLower");
907         inclusiveLowerXMLDef.addPropertyDefinition("textFieldWithInclusiveLower", "newvalue");
908         workflowDocument.addSearchableDefinition(inclusiveLowerXMLDef.build());
909         // adding case-sensitive searchable attribute
910         WorkflowAttributeDefinition.Builder caseSensitiveXMLDef = WorkflowAttributeDefinition.Builder.create("TextFieldWithCaseSensitivity");
911         caseSensitiveXMLDef.addPropertyDefinition("textFieldWithCaseSensitivity", "thevalue");
912         workflowDocument.addSearchableDefinition(caseSensitiveXMLDef.build());
913         // adding searchable attribute with overridden properties
914         WorkflowAttributeDefinition.Builder overridesXMLDef = WorkflowAttributeDefinition.Builder.create("TextFieldWithOverrides");
915         overridesXMLDef.addPropertyDefinition("textFieldWithOverrides", "SomeVal");
916         workflowDocument.addSearchableDefinition(overridesXMLDef.build());
917 
918         workflowDocument.setTitle("Range Def Test");
919         workflowDocument.route("routing range def doc.");
920 
921         workflowDocument = WorkflowDocumentFactory.loadDocument(principalId, workflowDocument.getDocumentId());
922 
923         // Verify that the "TextFieldWithInclusiveLower" attribute behaves as expected (lower-bound-inclusive and (by default) case-insensitive).
924         /* upper bound is set to inclusive in assertSearchBehavesAsExpected, second case should match */
925         assertSearchBehavesAsExpected(docType, principalId, "textFieldWithInclusiveLower",
926                 new String[] { "newvalue", ""        , ""        , "NEWVALUD", "newValuf", "newValuj", "newvaluf"},
927                 new String[] { ""        , "newvalue", "Newvaluf", "NEWVALUF", "newValud", "NEWVALUK", ""        },
928                 new int[]    { 1         , 1         , 1         , 1         , -1        , 0         , 0         });
929 
930         // Verify that the "TextFieldWithCaseSensitivity" attribute behaves as expected (bound-inclusive and case-sensitive).
931         assertSearchBehavesAsExpected(docType, principalId, "textFieldWithCaseSensitivity",
932         		new String[] { "thevalue", ""        , ""        , "THEVALUD", "thevalud", "Thevalud", "THEVALUF"},
933         		new String[] { ""        , "thevalue", "Thevalue", "THEVALUF", "THEVALUF", "Thevaluf", ""        },
934         		new int[]    { 1         , 1         , 0         , 0         , -1        , 0         , 1         });
935 
936         // Verify that the "TextFieldWithOverrides" attribute behaves as expected
937         // FIXME: KULRICE-5630 need to update these tests to specify expressions instead of using addSearchableAttribute range, which does not
938         // support exclusive lower
939         assertSearchBehavesAsExpected(docType, principalId, "textFieldWithOverrides",
940         		new String[] { "someval", "SomeVal", ""       , ""       , "SOMEVAK", "SomeVam", "SOMEVAM", "somevak", ""       },
941         		new String[] { ""       , ""       , "SOMEVAL", "SomeVal", "SomeVam", "SOMEVAK", "SomeVak", ""       , "SomeVak"},
942         		new int[]    { 0        , 0        , 0        , 1        , 1        , 0       , 0        , 1        , 0        });
943     }
944 
945     /*
946      * A convenience method for performing document-searching operations involving range definitions. The array parameters must all be the same length,
947      * since this method will perform tests with the values given by entries located at the same indices.
948      * @param docType The document type to search for.
949      * @param principalId The ID of the user that will perform the search.
950      * @param fieldDefKey The name of the field given by the field definition on the searchable attribute.
951      * @param lowBounds The lower bounds to use in the tests; to ignore a lower bound for a test, use an empty String.
952      * @param upBounds The upper bounds to use in the tests; to ignore an upper bound for a test, use an empty String.
953      * @param resultSizes The expected number of documents to be returned by the search; use -1 to indicate that an exception should have occurred.
954      * @throws Exception
955      */
956     private void assertSearchBehavesAsExpected(DocumentType docType, String principalId, String fieldDefKey, String[] lowBounds, String[] upBounds,
957     		int[] resultSizes) throws Exception {
958         DocumentSearchCriteria.Builder criteria = null;
959         DocumentSearchResults results = null;
960         DocumentSearchService docSearchService = KEWServiceLocator.getDocumentSearchService();
961         for (int i = 0; i < resultSizes.length; i++) {
962         	criteria = DocumentSearchCriteria.Builder.create();
963         	criteria.setDocumentTypeName(docType.getName());
964             addSearchableAttributeRange(criteria, fieldDefKey, lowBounds[i], upBounds[i], true);
965         	try {
966         		results = docSearchService.lookupDocuments(principalId, criteria.build());
967         		if (resultSizes[i] < 0) {
968         			fail(fieldDefKey + "'s search at loop index " + i + " should have thrown an exception");
969         		}
970         		assertEquals(fieldDefKey
971                         + "'s search results at loop index "
972                         + i
973                         + " returned the wrong number of documents.", resultSizes[i], results.getSearchResults().size());
974         	} catch (Exception ex) {
975         		if (resultSizes[i] >= 0) {
976         			fail(fieldDefKey + "'s search at loop index " + i + " should not have thrown an exception");
977         		}
978         	}
979         }
980     }
981 }