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;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.joda.time.DateTime;
20  import org.junit.Test;
21  import org.kuali.rice.kew.api.KewApiServiceLocator;
22  import org.kuali.rice.kew.api.WorkflowDocument;
23  import org.kuali.rice.kew.api.WorkflowDocumentFactory;
24  import org.kuali.rice.kew.api.document.DocumentContentUpdate;
25  import org.kuali.rice.kew.api.document.attribute.WorkflowAttributeDefinition;
26  import org.kuali.rice.kew.api.document.search.DocumentSearchCriteria;
27  import org.kuali.rice.kew.api.document.search.DocumentSearchResults;
28  import org.kuali.rice.kew.api.extension.ExtensionDefinition;
29  import org.kuali.rice.kew.docsearch.service.DocumentSearchService;
30  import org.kuali.rice.kew.docsearch.xml.StandardGenericXMLSearchableAttribute;
31  import org.kuali.rice.kew.doctype.bo.DocumentType;
32  import org.kuali.rice.kew.doctype.service.DocumentTypeService;
33  import org.kuali.rice.kew.exception.WorkflowServiceErrorException;
34  import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue;
35  import org.kuali.rice.kew.routeheader.service.RouteHeaderService;
36  import org.kuali.rice.kew.rule.bo.RuleAttribute;
37  import org.kuali.rice.kew.rule.service.RuleAttributeService;
38  import org.kuali.rice.kew.service.KEWServiceLocator;
39  import org.kuali.rice.kew.api.KewApiConstants;
40  import org.kuali.rice.kim.api.identity.Person;
41  import org.kuali.rice.kim.api.services.KimApiServiceLocator;
42  import org.kuali.rice.krad.util.GlobalVariables;
43  import org.kuali.rice.test.BaselineTestCase;
44  
45  import java.sql.Timestamp;
46  import java.util.ArrayList;
47  import java.util.Arrays;
48  import java.util.Collections;
49  import java.util.HashMap;
50  import java.util.List;
51  import java.util.Map;
52  
53  import static org.junit.Assert.*;
54  
55  
56  /**
57   * Tests the StandardGenericXMLSearchableAttribute.
58   *
59   * KULWF-654: Tests the resolution to this issue by configuring a CustomActionListAttribute as well as a
60   * searchable attribute.
61   */
62  public class SearchableAttributeTest extends DocumentSearchTestBase {
63  
64      protected void loadTestData() throws Exception {
65          loadXmlFile("SearchAttributeConfig.xml");
66          loadXmlFile("SearchableTrimTest.xml");
67      }
68  
69  //    private SearchAttributeCriteriaComponent createSearchAttributeCriteriaComponent(String key,String value,Boolean isLowerBoundValue,DocumentType docType) {
70  //    	String formKey = (isLowerBoundValue == null) ? key : ((isLowerBoundValue != null && isLowerBoundValue.booleanValue()) ? SearchableAttributeOld.RANGE_LOWER_BOUND_PROPERTY_PREFIX : SearchableAttributeOld.RANGE_UPPER_BOUND_PROPERTY_PREFIX);
71  //    	String savedKey = key;
72  //    	SearchAttributeCriteriaComponent sacc = new SearchAttributeCriteriaComponent(formKey,value,savedKey);
73  //    	Field field = getFieldByFormKey(docType, formKey);
74  //    	if (field != null) {
75  //        	sacc.setSearchableAttributeValue(DocSearchUtils.getSearchableAttributeValueByDataTypeString(field.getFieldDataType()));
76  //        	sacc.setRangeSearch(field.isMemberOfRange());
77  //        	sacc.setAllowWildcards(field.isAllowingWildcards());
78  //        	sacc.setAutoWildcardBeginning(field.isAutoWildcardAtBeginning());
79  //        	sacc.setAutoWildcardEnd(field.isAutoWildcardAtEnding());
80  //        	sacc.setCaseSensitive(field.isCaseSensitive());
81  //        	sacc.setSearchInclusive(field.isInclusive());
82  //            sacc.setSearchable(field.isSearchable());
83  //            sacc.setCanHoldMultipleValues(Field.MULTI_VALUE_FIELD_TYPES.contains(field.getFieldType()));
84  //    	}
85  //    	return sacc;
86  //    }
87  //
88  //    private Field getFieldByFormKey(DocumentType docType, String formKey) {
89  //    	if (docType == null) {
90  //    		return null;
91  //    	}
92  //		for (SearchableAttributeOld searchableAttribute : docType.getSearchableAttributesOld()) {
93  //			for (Row row : searchableAttribute.getSearchingRows()) {
94  //				for (Field field : row.getFields()) {
95  //					if (field.getPropertyName().equals(formKey)) {
96  //						return field;
97  //					}
98  //				}
99  //			}
100 //		}
101 //		return null;
102 //    }
103 
104     /**
105      * This tests the ability to get the searchableAttributeValues directly without going through the document.
106      */
107     @Test public void testSearchableAttributeSearch()throws Exception {
108     	String documentTypeName = "SearchDocType";
109         String userNetworkId = "rkirkend";
110         WorkflowDocument workflowDocument = WorkflowDocumentFactory.createDocument(getPrincipalId(userNetworkId), documentTypeName);
111         workflowDocument.setTitle("Routing style");
112         workflowDocument.route("routing this document.");
113 
114         workflowDocument = WorkflowDocumentFactory.loadDocument(getPrincipalId(userNetworkId), workflowDocument.getDocumentId());
115         DocumentRouteHeaderValue doc = KEWServiceLocator.getRouteHeaderService().getRouteHeader(workflowDocument.getDocumentId());
116 
117         /*
118         assertEquals("Wrong number of searchable attributes", 4, doc.getSearchableAttributeValues().size());
119 
120         for (Iterator<SearchableAttributeValue> iter = doc.getSearchableAttributeValues().iterator(); iter.hasNext();) {
121             SearchableAttributeValue attributeValue = iter.next();
122             if (attributeValue instanceof SearchableAttributeStringValue) {
123                 SearchableAttributeStringValue realValue = (SearchableAttributeStringValue) attributeValue;
124 
125                 for(String value:getRouteHeaderService().getSearchableAttributeStringValuesByKey(doc.getDocumentId(), realValue.getSearchableAttributeKey())){
126                 	assertEquals("Assert that the values are the same", value, attributeValue.getSearchableAttributeValue());
127                 }
128 
129             } else if (attributeValue instanceof SearchableAttributeLongValue) {
130                 SearchableAttributeLongValue realValue = (SearchableAttributeLongValue) attributeValue;
131                 for(Long value:getRouteHeaderService().getSearchableAttributeLongValuesByKey(doc.getDocumentId(), realValue.getSearchableAttributeKey())){
132                 	assertEquals("Assert that the values are the same", value, attributeValue.getSearchableAttributeValue());
133                 }
134             } else if (attributeValue instanceof SearchableAttributeFloatValue) {
135                 SearchableAttributeFloatValue realValue = (SearchableAttributeFloatValue) attributeValue;
136                 for(BigDecimal value:getRouteHeaderService().getSearchableAttributeFloatValuesByKey(doc.getDocumentId(), realValue.getSearchableAttributeKey())){
137                 	assertEquals("Assert that the values are the same", value, attributeValue.getSearchableAttributeValue());
138                 }
139 
140             } else if (attributeValue instanceof SearchableAttributeDateTimeValue) {
141                 SearchableAttributeDateTimeValue realValue = (SearchableAttributeDateTimeValue) attributeValue;
142                 assertEquals("The only DateTime attribute that should have been added has key '" + TestXMLSearchableAttributeDateTime.SEARCH_STORAGE_KEY + "'", TestXMLSearchableAttributeDateTime.SEARCH_STORAGE_KEY, realValue.getSearchableAttributeKey());
143 
144                 Calendar testDate = Calendar.getInstance();
145                 testDate.setTimeInMillis(realValue.getSearchableAttributeValue().getTime());
146                 testDate.set(Calendar.SECOND, 0);
147                 testDate.set(Calendar.MILLISECOND, 0);
148 
149                 for(Timestamp value:getRouteHeaderService().getSearchableAttributeDateTimeValuesByKey(doc.getDocumentId(), realValue.getSearchableAttributeKey())){
150                 	Calendar attributeDate = Calendar.getInstance();
151                     attributeDate.setTimeInMillis(value.getTime());
152                     attributeDate.set(Calendar.SECOND, 0);
153                     attributeDate.set(Calendar.MILLISECOND, 0);
154 
155                     assertEquals("The month value for the searchable attribute is wrong",testDate.get(Calendar.MONTH),attributeDate.get(Calendar.MONTH));
156                     assertEquals("The date value for the searchable attribute is wrong",testDate.get(Calendar.DATE),attributeDate.get(Calendar.DATE));
157                     assertEquals("The year value for the searchable attribute is wrong",testDate.get(Calendar.YEAR),attributeDate.get(Calendar.YEAR));
158                 }
159 
160             } else {
161                 fail("Searchable Attribute Value base class should be one of the four checked always");
162             }
163         }
164         */
165 
166     }
167 
168     protected RouteHeaderService getRouteHeaderService(){
169     	RouteHeaderService rRet = KEWServiceLocator.getRouteHeaderService();
170     	return rRet;
171     }
172 
173     protected String getPrincipalId(String networkId){
174     	return KimApiServiceLocator.getPersonService().getPersonByPrincipalName(networkId).getPrincipalId();
175     }
176 
177     @Test public void testCustomSearchableAttributesWithDataType() throws Exception {
178         String documentTypeName = "SearchDocType";
179     	DocumentType docType = ((DocumentTypeService)KEWServiceLocator.getService(KEWServiceLocator.DOCUMENT_TYPE_SERVICE)).findByName(documentTypeName);
180         String userNetworkId = "rkirkend";
181         WorkflowDocument workflowDocument = WorkflowDocumentFactory.createDocument(getPrincipalId(userNetworkId), documentTypeName);
182         workflowDocument.setTitle("Routing style");
183         workflowDocument.route("routing this document.");
184 
185         workflowDocument = WorkflowDocumentFactory.loadDocument(getPrincipalId(userNetworkId), workflowDocument.getDocumentId());
186         DocumentRouteHeaderValue doc = KEWServiceLocator.getRouteHeaderService().getRouteHeader(workflowDocument.getDocumentId());
187 
188         DocumentSearchService docSearchService = (DocumentSearchService) KEWServiceLocator.getService(KEWServiceLocator.DOCUMENT_SEARCH_SERVICE);
189         Person user = KimApiServiceLocator.getPersonService().getPersonByPrincipalName(userNetworkId);
190 
191         DocumentSearchCriteria.Builder criteria = DocumentSearchCriteria.Builder.create();
192         criteria.setDocumentTypeName(documentTypeName);
193         addSearchableAttribute(criteria, TestXMLSearchableAttributeString.SEARCH_STORAGE_KEY,
194                 TestXMLSearchableAttributeString.SEARCH_STORAGE_VALUE);
195         DocumentSearchResults results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
196         assertEquals("Search results should have one document.", 1, results.getSearchResults().size());
197 
198         criteria = DocumentSearchCriteria.Builder.create();
199         criteria.setDocumentTypeName(documentTypeName);
200         addSearchableAttribute(criteria, TestXMLSearchableAttributeString.SEARCH_STORAGE_KEY, "fred");
201         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
202         assertEquals("Search results should be empty.", 0, results.getSearchResults().size());
203 
204         criteria = DocumentSearchCriteria.Builder.create();
205         criteria.setDocumentTypeName(documentTypeName);
206         addSearchableAttribute(criteria, "fakeproperty", "doesntexist");
207         try {
208             docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
209             fail("Search results should be throwing a validation exception for use of non-existant searchable attribute");
210         } catch (RuntimeException e) {}
211 
212         criteria = DocumentSearchCriteria.Builder.create();
213         criteria.setDocumentTypeName(documentTypeName);
214         addSearchableAttribute(criteria, TestXMLSearchableAttributeLong.SEARCH_STORAGE_KEY,
215                 TestXMLSearchableAttributeLong.SEARCH_STORAGE_VALUE.toString());
216         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
217         assertEquals("Search results should have one document.", 1, results.getSearchResults().size());
218 
219         criteria = DocumentSearchCriteria.Builder.create();
220         criteria.setDocumentTypeName(documentTypeName);
221         addSearchableAttribute(criteria, TestXMLSearchableAttributeLong.SEARCH_STORAGE_KEY, "1111111");
222         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
223         assertEquals("Search results should be empty.", 0, results.getSearchResults().size());
224 
225         criteria = DocumentSearchCriteria.Builder.create();
226         criteria.setDocumentTypeName(documentTypeName);
227         addSearchableAttribute(criteria, "fakeymcfakefake", "99999999");
228         try {
229             docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
230             fail("Search results should be throwing a validation exception for use of non-existant searchable attribute");
231         } catch (RuntimeException e) {}
232 
233         criteria = DocumentSearchCriteria.Builder.create();
234         criteria.setDocumentTypeName(documentTypeName);
235         addSearchableAttribute(criteria, TestXMLSearchableAttributeFloat.SEARCH_STORAGE_KEY,
236                 TestXMLSearchableAttributeFloat.SEARCH_STORAGE_VALUE.toString());
237         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
238         assertEquals("Search results should have one document.", 1, results.getSearchResults().size());
239 
240         criteria = DocumentSearchCriteria.Builder.create();
241         criteria.setDocumentTypeName(documentTypeName);
242         addSearchableAttribute(criteria, TestXMLSearchableAttributeFloat.SEARCH_STORAGE_KEY, "215.3548");
243         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
244         assertEquals("Search results should be empty.", 0, results.getSearchResults().size());
245 
246         criteria = DocumentSearchCriteria.Builder.create();
247         criteria.setDocumentTypeName(documentTypeName);
248         addSearchableAttribute(criteria, "fakeylostington", "9999.9999");
249         try {
250             docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
251             fail("Search results should be throwing a validation exception for use of non-existant searchable attribute");
252         } catch (RuntimeException e) {}
253 
254         criteria = DocumentSearchCriteria.Builder.create();
255         criteria.setDocumentTypeName(documentTypeName);
256         addSearchableAttribute(criteria, TestXMLSearchableAttributeDateTime.SEARCH_STORAGE_KEY,
257                 DocumentSearchInternalUtils.getDisplayValueWithDateOnly(new Timestamp(
258                         TestXMLSearchableAttributeDateTime.SEARCH_STORAGE_VALUE_IN_MILLS)));
259         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
260         assertEquals("Search results should have one document.", 1, results.getSearchResults().size());
261 
262         criteria = DocumentSearchCriteria.Builder.create();
263         criteria.setDocumentTypeName(documentTypeName);
264         addSearchableAttribute(criteria, TestXMLSearchableAttributeDateTime.SEARCH_STORAGE_KEY, "07/06/1979");
265         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
266         assertEquals("Search results should be empty.", 0, results.getSearchResults().size());
267 
268         criteria = DocumentSearchCriteria.Builder.create();
269         criteria.setDocumentTypeName(documentTypeName);
270         addSearchableAttribute(criteria, "lastingsfakerson", "07/06/2007");
271         try {
272             docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
273             fail("Search results should be throwing a validation exception for use of non-existant searchable attribute");
274         } catch (RuntimeException e) {}
275     }
276 
277     /**
278      * Tests searching documents with searchable attributes
279      * @throws org.kuali.rice.kew.api.exception.WorkflowException
280      */
281     @Test public void testSearchAttributesAcrossDocumentTypeVersions() throws Exception {
282         // first test searching for an initial version of the doc which does not have a searchable attribute
283         loadXmlFile("testdoc0.xml");
284 
285         String documentTypeName = "SearchDoc";
286         WorkflowDocument doc = WorkflowDocumentFactory.createDocument(getPrincipalIdForName("arh14"), documentTypeName);
287         DocumentType docType = ((DocumentTypeService)KEWServiceLocator.getService(KEWServiceLocator.DOCUMENT_TYPE_SERVICE)).findByName(documentTypeName);
288         doc.route("routing");
289 
290         DocumentSearchService docSearchService = (DocumentSearchService) KEWServiceLocator.getService(KEWServiceLocator.DOCUMENT_SEARCH_SERVICE);
291 
292         DocumentSearchCriteria.Builder criteria = DocumentSearchCriteria.Builder.create();
293         criteria.setDocumentTypeName(documentTypeName);
294         criteria.setDateCreatedFrom(new DateTime(2004, 1, 1, 0, 0));
295 
296         Person user = KimApiServiceLocator.getPersonService().getPersonByPrincipalName("arh14");
297         DocumentSearchResults results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
298         assertEquals(1, results.getSearchResults().size());
299 
300         // now upload the new version with a searchable attribute
301         loadXmlFile("testdoc1.xml");
302         docType = ((DocumentTypeService)KEWServiceLocator.getService(KEWServiceLocator.DOCUMENT_TYPE_SERVICE)).findByName(documentTypeName);
303 
304         // route a new doc
305         doc = WorkflowDocumentFactory.createDocument(getPrincipalIdForName("arh14"), documentTypeName);
306         doc.route("routing");
307 
308         // with no attribute criteria, both docs should be found
309         criteria = DocumentSearchCriteria.Builder.create();
310         criteria.setDocumentTypeName(documentTypeName);
311         criteria.setDateCreatedFrom(new DateTime(2004, 1, 1, 0, 0));
312 
313         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
314         assertEquals(2, results.getSearchResults().size());
315 
316         // search with specific SearchableAttributeOld value
317         criteria = DocumentSearchCriteria.Builder.create();
318         criteria.setDocumentTypeName(documentTypeName);
319         criteria.setDateCreatedFrom(new DateTime(2004, 1, 1, 0, 0));
320         addSearchableAttribute(criteria, "MockSearchableAttributeKey", "MockSearchableAttributeValue");
321 
322         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
323         assertEquals(1, results.getSearchResults().size());
324 
325         // search with any SearchableAttributeOld value
326         criteria = DocumentSearchCriteria.Builder.create();
327         criteria.setDocumentTypeName(documentTypeName);
328         criteria.setDateCreatedFrom(new DateTime(2004, 1, 1, 0, 0));
329         addSearchableAttribute(criteria, "MockSearchableAttributeKey", "");
330 
331         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
332         // should return two because an empty value above will return any value of the 'MockSearchableAttributeKey' key including the previous document
333         // that doesn't even have a record of that field being saved to the database
334         assertEquals(2, results.getSearchResults().size());
335     }
336 
337     /**
338      * Tests the usage of wildcards on searchable attributes of varying data types.
339      * Note that the bounds of ".."-related search expressions will not throw an exception if the lower bound is greater than the upper bound;
340      * instead, such an expression will simply return zero results.
341      * @throws Exception
342      */
343     @Test public void testWildcardsOnSearchableAttributes() throws Exception {
344         String documentTypeName = "WildcardTestDocType";
345     	DocumentType docType = KEWServiceLocator.getDocumentTypeService().findByName(documentTypeName);
346         String principalName = "rkirkend";
347         String principalId = KimApiServiceLocator.getPersonService().getPersonByPrincipalName(principalName).getPrincipalId();
348         String[][] searchableAttributeValuesAsStrings = { {"testString", "9984", "38.1357", "06/24/2009"},
349         		{"anotherStr", "33", "80000.65432", "07/08/2010"}, {"MoreText", "432", "-0.765", "12/12/2012"} };
350 
351         // Route some documents containing the searchable attribute values given by the above array.
352         for (int i = 0; i < searchableAttributeValuesAsStrings.length; i++) {
353         	WorkflowDocument workflowDocument = WorkflowDocumentFactory.createDocument(principalId, documentTypeName);
354 
355         	// Add the string searchable attribute.
356         	WorkflowAttributeDefinition.Builder wcStringXMLDef = WorkflowAttributeDefinition.Builder.create("XMLSearchableAttributeWildcardString");
357         	wcStringXMLDef.addPropertyDefinition("xmlSearchableAttributeWildcardString", searchableAttributeValuesAsStrings[i][0]);
358         	workflowDocument.addSearchableDefinition(wcStringXMLDef.build());
359         	// Add the long searchable attribute.
360         	WorkflowAttributeDefinition.Builder wcLongXMLDef = WorkflowAttributeDefinition.Builder.create("XMLSearchableAttributeWildcardLong");
361         	wcLongXMLDef.addPropertyDefinition("xmlSearchableAttributeWildcardLong", searchableAttributeValuesAsStrings[i][1]);
362         	workflowDocument.addSearchableDefinition(wcLongXMLDef.build());
363         	// Add the float searchable attribute.
364         	WorkflowAttributeDefinition.Builder wcFloatXMLDef = WorkflowAttributeDefinition.Builder.create("XMLSearchableAttributeWildcardFloat");
365         	wcFloatXMLDef.addPropertyDefinition("xmlSearchableAttributeWildcardFloat", searchableAttributeValuesAsStrings[i][2]);
366         	workflowDocument.addSearchableDefinition(wcFloatXMLDef.build());
367         	// Add the datetime searchable attribute.
368         	WorkflowAttributeDefinition.Builder wcDatetimeXMLDef = WorkflowAttributeDefinition.Builder.create("XMLSearchableAttributeWildcardDatetime");
369         	wcDatetimeXMLDef.addPropertyDefinition("xmlSearchableAttributeWildcardDatetime", searchableAttributeValuesAsStrings[i][3]);
370         	workflowDocument.addSearchableDefinition(wcDatetimeXMLDef.build());
371 
372         	workflowDocument.setTitle("Search Def Test Doc " + i);
373         	workflowDocument.route("routing search def doc " + i);
374         }
375 
376         // Ensure that wildcards work on searchable string attributes. Note that this search should be case-insensitive by default.
377         // Also note that this should be the only case where the string-specific wildcards ("!", "?", and "*") should be working, unless
378         // they are being used in a range expression.
379         assertSearchableAttributeWildcardsWork(docType, principalId, "xmlSearchableAttributeWildcardString",
380         		new String[]  {"TESTSTRING|moretext", "!MoreText"   , "!anotherStr!testString", "!anotherStr&&!MoreText"  , "!SomeString"      ,
381         					"*str*"                 , "More????"    , "*e*n?"                 , "???String"               , "*te*&&!????String", "!test??????"       , "anotherStr..MoreText",
382         					"testString..MoreText"  , ">=testString", "<=anotherStr|>MoreText", "<testString&&!anotherStr", ">abc"             , "<anotherOne&&>text",
383         					">More????"             , "<*test*"},
384         			new int[] {2                    , 2             , 1                       , 1                         , 3                  ,
385         					2                       , 1             , 1                       , 0                         , 1                  , 2                   , 2 /*1*/               ,
386         					0                       , 1             , 2                       , 1                         , 3                  , 0                   ,
387         					2                       , 2});
388         
389         // ensure multiple values work
390         assertSearchableAttributeMultiplesWork(docType, principalId, "xmlSearchableAttributeWildcardString",
391         		new String[][] { {"testString"}, {"anotherStr"}, {"MoreText"}, {"testString", "anotherStr"}, {"testString", "MoreText"}, {"anotherStr", "MoreText"}, {"testString", "anotherStr", "MoreText"}, {"monkey"}, {"monkey", "giraffe"}, {"monkey", "testString"} },
392         			new int[]  {  1,              1,              1,            2,                            2,                          2,                          3,                                        0,          0,                     1                       });
393 
394         // Ensure that wildcards work on searchable long attributes, and ensure the string-specific wildcards are not being utilized.
395         assertSearchableAttributeWildcardsWork(docType, principalId, "xmlSearchableAttributeWildcardLong",
396         		new String[]  {"99??", "*2"       , "!33"         , "<9984", ">432", "<=33", ">=432", ">33&&<9984", "<=100000&&>=20", ">9984&&<33", "432..9984",
397         					"9999..1", "<432|>432", ">=9000|<=100", "!", ">-76"},
398         			new int[] {-1     , -1          , 1             , 2      , 1     , 1     , 2      , 1           , 3               , 0           , 2 /*1*/    ,
399         					0        , 2          , 2             , -1 , 3});
400         
401         // ensure multiple values work
402         assertSearchableAttributeMultiplesWork(docType, principalId, "xmlSearchableAttributeWildcardLong",
403         		new String[][] { {"9984"}, {"33"}, {"432"}, {"9984", "33"}, {"9984", "432"}, {"33", "432"}, {"9984", "33", "432"}, {"7"}, {"7", "4488"}, {"7", "9984"} },
404         			new int[]  {  1,              1,              1,            2,                            2,                          2,                          3,                                        0,          0,                     1                       });
405 
406         // Ensure that wildcards work on searchable float attributes, and ensure the string-specific wildcards are not being utilized.
407         assertSearchableAttributeWildcardsWork(docType, principalId, "xmlSearchableAttributeWildcardFloat",
408         		new String[]  {"38.1???", "!-0.765", "*80*"                , "<80000.65432"   , ">0"                  , "<=-0.765", ">=38.1357", "<38.1358", "<-0.5|>0.5", ">=-0.765&&<=-0.765", ">38.1357&&<80000.65432",
409         					"-50..50"   , "100..10", "<=38.1357|>=38.1357" , ">123.4567|<0.11", "-1.1..38.1357&&<3.3"},
410         			new int[] {-1        , 1        , -1                     , 2                , 2                     , 1         , 2          , 2         , 3           , 1                   , 0                       ,
411         					2           , 0        , 3                     , 2                , 1});
412         
413         // ensure multiple values work
414         assertSearchableAttributeMultiplesWork(docType, principalId, "xmlSearchableAttributeWildcardFloat",
415         		new String[][] { {"38.1357"}, {"80000.65432"}, {"-0.765"}, {"38.1357", "80000.65432"}, {"38.1357", "-0.765"}, {"80000.65432", "-0.765"}, {"38.1357", "80000.65432", "-0.765"}, {"3.1415928"}, {"3.1415928", "4488.0"}, {"3.1415928", "38.1357"} },
416         			new int[]  {  1,              1,              1,            2,                            2,                          2,                          3,                                        0,          0,                     1                       });
417 
418 
419         // Ensure that wildcards work on searchable datetime attributes, and ensure the string-specific wildcards are not being utilized.
420         /* 06/24/2009, 07/08/2010, 12/12/2012 */
421         assertSearchableAttributeWildcardsWork(docType, principalId, "xmlSearchableAttributeWildcardDatetime",
422         		new String[]  {"??/??/20??"            , "12/12/20*"               , "!07/08/2010"           , ">06/24/2009", "<07/08/2010", ">=12/12/2012", "<=05/06/2011", ">06/24/2009&&<=07/08/2010",
423         					">=01/01/2001&&<06/24/2009", "11/29/1990..12/31/2009"  , "12/13/2100..08/09/1997",
424         					"<06/24/2009|>=12/12/2012" , "<=06/24/2009|>07/08/2010", ">02/31/2011"},
425         			new int[] {-1                      , -1                         , -1                      , 2            , 1            , 1             , 2             , 1                          ,
426         					0                          , 1                         , 0                       ,
427         					1                          , 2                         , -1});
428         
429         // ensure multiple values work
430         assertSearchableAttributeMultiplesWork(docType, principalId, "xmlSearchableAttributeWildcardDatetime",
431         		new String[][] { {"06/24/2009"}, {"07/08/2010"}, {"12/12/2012"}, {"06/24/2009", "07/08/2010"}, {"06/24/2009", "12/12/2012"}, {"07/08/2010", "12/12/2012"}, {"06/24/2009", "07/08/2010", "12/12/2012"}, {"12/20/2012"}, {"12/20/2012", "11/09/2009"}, {"12/20/2012", "12/12/2012"} },
432         			new int[]  {  1,              1,              1,            2,                            2,                          2,                          3,                                        0,          0,                     1                       });
433 
434     }
435 
436     /**
437      * A convenience method for testing wildcards on searchable attributes.
438      *
439      * @param docType The document type containing the attributes.
440      * @param principalId The ID of the user performing the search.
441      * @param fieldDefKey The name of the field given by the field definition on the searchable attribute.
442      * @param searchValues The wildcard-filled search strings to test.
443      * @param resultSizes The number of expected documents to be returned by the search; use -1 to indicate that an error should have occurred.
444      * @throws Exception
445      */
446     private void assertSearchableAttributeWildcardsWork(DocumentType docType, String principalId, String fieldDefKey, String[] searchValues,
447     		int[] resultSizes) throws Exception {
448     	DocumentSearchCriteria.Builder criteria = null;
449         DocumentSearchResults results = null;
450         DocumentSearchService docSearchService = KEWServiceLocator.getDocumentSearchService();
451         for (int i = 0; i < resultSizes.length; i++) {
452         	criteria = DocumentSearchCriteria.Builder.create();
453         	criteria.setDocumentTypeName(docType.getName());
454         	addSearchableAttribute(criteria, fieldDefKey, searchValues[i]);
455         	try {
456         		results = docSearchService.lookupDocuments(principalId, criteria.build());
457         		if (resultSizes[i] < 0) {
458         			fail(fieldDefKey + "'s search at loop index " + i + " should have thrown an exception");
459         		}
460         		if(resultSizes[i] != results.getSearchResults().size()){
461         			assertEquals(fieldDefKey + "'s search results at loop index " + i + " returned the wrong number of documents.", resultSizes[i], results.getSearchResults().size());
462         		}
463         	} catch (Exception ex) {
464         		if (resultSizes[i] >= 0) {
465         			fail(fieldDefKey + "'s search at loop index " + i + " should not have thrown an exception");
466         		}
467         	}
468         	GlobalVariables.clear();
469         }
470     }
471     
472     /**
473      * A convenience method for testing multiple value fields on searchable attributes.
474      *
475      * @param docType The document type containing the attributes.
476      * @param principalId The ID of the user performing the search.
477      * @param fieldDefKey The name of the field given by the field definition on the searchable attribute.
478      * @param searchValues The wildcard-filled search strings to test.
479      * @param resultSizes The number of expected documents to be returned by the search; use -1 to indicate that an error should have occurred.
480      * @throws Exception
481      */
482     private void assertSearchableAttributeMultiplesWork(DocumentType docType, String principalId, String fieldDefKey, String[][] searchValues,
483     		int[] resultSizes) throws Exception {
484         DocumentSearchCriteria.Builder criteria = null;
485         DocumentSearchResults results = null;
486         DocumentSearchService docSearchService = KEWServiceLocator.getDocumentSearchService();
487         for (int i = 0; i < resultSizes.length; i++) {
488             criteria = DocumentSearchCriteria.Builder.create();
489         	criteria.setDocumentTypeName(docType.getName());
490         	addSearchableAttribute(criteria, fieldDefKey, searchValues[i]);
491         	try {
492                 results = docSearchService.lookupDocuments(principalId, criteria.build());
493                 if (resultSizes[i] < 0) {
494         			fail(fieldDefKey + "'s search at loop index " + i + " should have thrown an exception");
495         		}
496         		if(resultSizes[i] != results.getSearchResults().size()){
497         			assertEquals(fieldDefKey + "'s search results at loop index " + i + " returned the wrong number of documents.", resultSizes[i], results.getSearchResults().size());
498         		}
499         	} catch (Exception ex) {
500         		if (resultSizes[i] >= 0) {
501         			fail(fieldDefKey + "'s search at loop index " + i + " should not have thrown an exception");
502         		}
503         	}
504         	GlobalVariables.clear();
505         }
506     }
507     
508     
509     
510     /**
511      * Per KULRICE-3681, tests that StandardGenericXMLSearchableAttribute throws no cast class exception when it shouldn't
512      */
513     @Test
514     public void testValidateUserSearchInputsNoCast() {
515     	StandardGenericXMLSearchableAttribute searchableAttribute = new StandardGenericXMLSearchableAttribute();
516     	final RuleAttributeService ruleAttributeService = KEWServiceLocator.getRuleAttributeService();
517         ExtensionDefinition extensionDefinition = KewApiServiceLocator.getExtensionRepositoryService().getExtensionByName("SearchableAttributeVisible");
518 
519         DocumentSearchCriteria.Builder criteria = DocumentSearchCriteria.Builder.create();
520     	Map<String, List<String>> simpleParamMap = new HashMap<String, List<String>>();
521     	simpleParamMap.put("givenname", Collections.singletonList("test"));
522         criteria.setDocumentAttributeValues(simpleParamMap);
523     	List errors = new ArrayList();
524     	Exception caughtException = null;
525     	try {
526     		errors = searchableAttribute.validateDocumentAttributeCriteria(extensionDefinition, criteria.build());
527     	} catch (RuntimeException re) {
528     		caughtException = re;
529     	}
530     	assertNull("Found exception "+caughtException, caughtException);
531     	assertTrue("Found errors "+errors, (errors.size() == 0));
532     	
533     	Map<String, List<String>> listParamMap = new HashMap<String, List<String>>();
534     	List<String> multipleValues = new ArrayList<String>();
535     	multipleValues.add("testone");
536     	multipleValues.add("testtwo");
537     	listParamMap.put("givenname", multipleValues);
538         criteria.setDocumentAttributeValues(listParamMap);
539     	errors = new ArrayList();
540     	caughtException = null;
541     	try {
542     		errors = searchableAttribute.validateDocumentAttributeCriteria(extensionDefinition, criteria.build());
543     	} catch (RuntimeException re) {
544     		caughtException = re;
545     	}
546     	assertNull("Found exception "+caughtException, caughtException);
547     	assertTrue("Found errors "+errors, (errors.size() == 0));
548     	
549     }
550     
551     @Test
552     public void testSearchableAttributeTrim() {
553     	RuleAttribute trimAttribute = KEWServiceLocator.getRuleAttributeService().findByName("TrimSearchableAttribute");
554     	assert(trimAttribute.getName().equals("TrimSearchableAttribute"));
555     	assert(trimAttribute.getResourceDescriptor().equals("org.kuali.rice.kew.docsearch.xml.StandardGenericXMLSearchableAttribute"));
556     	assert(trimAttribute.getLabel().equals("Unit111"));
557     	assert(trimAttribute.getType().equals("SearchableXmlAttribute"));
558     	assert(trimAttribute.getDescription().equals("Unit111"));
559     	assert(trimAttribute.getApplicationId().equals("NSTrimSearchableTest"));
560     	//System.out.println(trimAttribute.getName());
561     }
562 
563     @Test
564     public void testXmlGeneration() {
565         loadXmlFile("testdoc1.xml");
566         WorkflowAttributeDefinition searchableDefinition = WorkflowAttributeDefinition.Builder.create("SearchAttribute").build();
567         DocumentContentUpdate.Builder documentContentUpdateBuilder = DocumentContentUpdate.Builder.create();
568         documentContentUpdateBuilder.getSearchableDefinitions().add(searchableDefinition);
569         WorkflowDocument workflowDocument = WorkflowDocumentFactory.createDocument(getPrincipalId("ewestfal"), "SearchDoc", null, documentContentUpdateBuilder.build());
570         workflowDocument.route("");
571         assertTrue(workflowDocument.isFinal());
572         assertEquals(StringUtils.deleteWhitespace("<" + KewApiConstants.SEARCHABLE_CONTENT_ELEMENT + ">" + MockSearchableAttribute.SEARCH_CONTENT + "</" + KewApiConstants.SEARCHABLE_CONTENT_ELEMENT + ">"),
573                 StringUtils.deleteWhitespace(workflowDocument.getDocumentContent().getSearchableContent()));
574     }
575     
576 }