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