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