View Javadoc
1   /**
2    * Copyright 2005-2013 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.KewApiConstants;
22  import org.kuali.rice.kew.api.KewApiServiceLocator;
23  import org.kuali.rice.kew.api.WorkflowDocument;
24  import org.kuali.rice.kew.api.WorkflowDocumentFactory;
25  import org.kuali.rice.kew.api.document.DocumentContentUpdate;
26  import org.kuali.rice.kew.api.document.attribute.WorkflowAttributeDefinition;
27  import org.kuali.rice.kew.api.document.search.DocumentSearchCriteria;
28  import org.kuali.rice.kew.api.document.search.DocumentSearchResults;
29  import org.kuali.rice.kew.api.extension.ExtensionDefinition;
30  import org.kuali.rice.kew.docsearch.service.DocumentSearchService;
31  import org.kuali.rice.kew.docsearch.xml.StandardGenericXMLSearchableAttribute;
32  import org.kuali.rice.kew.doctype.bo.DocumentType;
33  import org.kuali.rice.kew.doctype.service.DocumentTypeService;
34  import org.kuali.rice.kew.impl.document.search.DocumentSearchCriteriaTranslatorImpl;
35  import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue;
36  import org.kuali.rice.kew.routeheader.dao.DocumentRouteHeaderDAO;
37  import org.kuali.rice.kew.routeheader.service.RouteHeaderService;
38  import org.kuali.rice.kew.rule.bo.RuleAttribute;
39  import org.kuali.rice.kew.service.KEWServiceLocator;
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.krad.util.KRADConstants;
44  
45  import java.math.BigDecimal;
46  import java.sql.Timestamp;
47  import java.util.ArrayList;
48  import java.util.Calendar;
49  import java.util.Collection;
50  import java.util.Collections;
51  import java.util.HashMap;
52  import java.util.List;
53  import java.util.Map;
54  
55  import static org.junit.Assert.*;
56  
57  /**
58   * Tests the StandardGenericXMLSearchableAttribute.
59   *
60   * KULWF-654: Tests the resolution to this issue by configuring a CustomActionListAttribute as well as a
61   * searchable attribute.
62   */
63  public class SearchableAttributeTest extends DocumentSearchTestBase {
64  
65      protected void loadTestData() throws Exception {
66          loadXmlFile("SearchAttributeConfig.xml");
67          loadXmlFile("SearchableTrimTest.xml");
68      }
69  
70  //    private SearchAttributeCriteriaComponent createSearchAttributeCriteriaComponent(String key,String value,Boolean isLowerBoundValue,DocumentType docType) {
71  //    	String formKey = (isLowerBoundValue == null) ? key : ((isLowerBoundValue != null && isLowerBoundValue.booleanValue()) ? SearchableAttributeOld.RANGE_LOWER_BOUND_PROPERTY_PREFIX : SearchableAttributeOld.RANGE_UPPER_BOUND_PROPERTY_PREFIX);
72  //    	String savedKey = key;
73  //    	SearchAttributeCriteriaComponent sacc = new SearchAttributeCriteriaComponent(formKey,value,savedKey);
74  //    	Field field = getFieldByFormKey(docType, formKey);
75  //    	if (field != null) {
76  //        	sacc.setSearchableAttributeValue(DocSearchUtils.getSearchableAttributeValueByDataTypeString(field.getFieldDataType()));
77  //        	sacc.setRangeSearch(field.isMemberOfRange());
78  //        	sacc.setAllowWildcards(field.isAllowingWildcards());
79  //        	sacc.setAutoWildcardBeginning(field.isAutoWildcardAtBeginning());
80  //        	sacc.setAutoWildcardEnd(field.isAutoWildcardAtEnding());
81  //        	sacc.setCaseSensitive(field.isCaseSensitive());
82  //        	sacc.setSearchInclusive(field.isInclusive());
83  //            sacc.setSearchable(field.isSearchable());
84  //            sacc.setCanHoldMultipleValues(Field.MULTI_VALUE_FIELD_TYPES.contains(field.getFieldType()));
85  //    	}
86  //    	return sacc;
87  //    }
88  //
89  //    private Field getFieldByFormKey(DocumentType docType, String formKey) {
90  //    	if (docType == null) {
91  //    		return null;
92  //    	}
93  //		for (SearchableAttributeOld searchableAttribute : docType.getSearchableAttributesOld()) {
94  //			for (Row row : searchableAttribute.getSearchingRows()) {
95  //				for (Field field : row.getFields()) {
96  //					if (field.getPropertyName().equals(formKey)) {
97  //						return field;
98  //					}
99  //				}
100 //			}
101 //		}
102 //		return null;
103 //    }
104 
105     /**
106      * This tests the ability to get the searchableAttributeValues directly without going through the document.
107      */
108     @Test public void testSearchableAttributeSearch()throws Exception {
109     	String documentTypeName = "SearchDocType";
110         String userNetworkId = "rkirkend";
111         WorkflowDocument workflowDocument = WorkflowDocumentFactory.createDocument(getPrincipalId(userNetworkId), documentTypeName);
112         workflowDocument.setTitle("Routing style");
113         workflowDocument.route("routing this document.");
114 
115         workflowDocument = WorkflowDocumentFactory.loadDocument(getPrincipalId(userNetworkId), workflowDocument.getDocumentId());
116         DocumentRouteHeaderValue doc = KEWServiceLocator.getRouteHeaderService().getRouteHeader(workflowDocument.getDocumentId());
117 
118         // HACK: we are cheating, but this functionality was removed from the service apis, so we hit the DAO directly
119         DocumentRouteHeaderDAO dao = KEWServiceLocator.getBean("enDocumentRouteHeaderDAO");
120         Collection<SearchableAttributeValue> allValues = dao.findSearchableAttributeValues(workflowDocument.getDocumentId());
121         assertEquals("Wrong number of searchable attributes", 4, allValues.size());
122 
123         for (SearchableAttributeValue attributeValue: allValues) {
124             if (attributeValue instanceof SearchableAttributeStringValue) {
125                 SearchableAttributeStringValue realValue = (SearchableAttributeStringValue) attributeValue;
126 
127                 for(String value:getRouteHeaderService().getSearchableAttributeStringValuesByKey(doc.getDocumentId(), realValue.getSearchableAttributeKey())){
128                 	assertEquals("Assert that the values are the same", value, attributeValue.getSearchableAttributeValue());
129                 }
130 
131             } else if (attributeValue instanceof SearchableAttributeLongValue) {
132                 SearchableAttributeLongValue realValue = (SearchableAttributeLongValue) attributeValue;
133                 for(Long value:getRouteHeaderService().getSearchableAttributeLongValuesByKey(doc.getDocumentId(), realValue.getSearchableAttributeKey())){
134                 	assertEquals("Assert that the values are the same", value, attributeValue.getSearchableAttributeValue());
135                 }
136             } else if (attributeValue instanceof SearchableAttributeFloatValue) {
137                 SearchableAttributeFloatValue realValue = (SearchableAttributeFloatValue) attributeValue;
138                 for(BigDecimal value:getRouteHeaderService().getSearchableAttributeFloatValuesByKey(doc.getDocumentId(), realValue.getSearchableAttributeKey())){
139                 	assertEquals("Assert that the values are the same", value, attributeValue.getSearchableAttributeValue());
140                 }
141 
142             } else if (attributeValue instanceof SearchableAttributeDateTimeValue) {
143                 SearchableAttributeDateTimeValue realValue = (SearchableAttributeDateTimeValue) attributeValue;
144                 assertEquals("The only DateTime attribute that should have been added has key '" + TestXMLSearchableAttributeDateTime.SEARCH_STORAGE_KEY + "'", TestXMLSearchableAttributeDateTime.SEARCH_STORAGE_KEY, realValue.getSearchableAttributeKey());
145 
146                 Calendar testDate = Calendar.getInstance();
147                 testDate.setTimeInMillis(realValue.getSearchableAttributeValue().getTime());
148                 testDate.set(Calendar.SECOND, 0);
149                 testDate.set(Calendar.MILLISECOND, 0);
150 
151                 for(Timestamp value:getRouteHeaderService().getSearchableAttributeDateTimeValuesByKey(doc.getDocumentId(), realValue.getSearchableAttributeKey())){
152                 	Calendar attributeDate = Calendar.getInstance();
153                     attributeDate.setTimeInMillis(value.getTime());
154                     attributeDate.set(Calendar.SECOND, 0);
155                     attributeDate.set(Calendar.MILLISECOND, 0);
156 
157                     assertEquals("The month value for the searchable attribute is wrong",testDate.get(Calendar.MONTH),attributeDate.get(Calendar.MONTH));
158                     assertEquals("The date value for the searchable attribute is wrong",testDate.get(Calendar.DATE),attributeDate.get(Calendar.DATE));
159                     assertEquals("The year value for the searchable attribute is wrong",testDate.get(Calendar.YEAR),attributeDate.get(Calendar.YEAR));
160                 }
161 
162             } else {
163                 fail("Searchable Attribute Value base class should be one of the four checked always");
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 
330         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
331 
332         assertEquals(2, results.getSearchResults().size());
333     }
334 
335     /**
336      * Tests the usage of wildcards on searchable attributes of varying data types.
337      * Note that the bounds of ".."-related search expressions will not throw an exception if the lower bound is greater than the upper bound;
338      * instead, such an expression will simply return zero results.
339      * @throws Exception
340      */
341     @Test public void testWildcardsOnSearchableAttributes() throws Exception {
342         String documentTypeName = "WildcardTestDocType";
343     	DocumentType docType = KEWServiceLocator.getDocumentTypeService().findByName(documentTypeName);
344         String principalName = "rkirkend";
345         String principalId = KimApiServiceLocator.getPersonService().getPersonByPrincipalName(principalName).getPrincipalId();
346         String[][] searchableAttributeValuesAsStrings = { {"testString", "9984", "38.1357", "06/24/2009"},
347         		{"anotherStr", "33", "80000.65432", "07/08/2010"}, {"MoreText", "432", "-0.765", "12/12/2012"} };
348 
349         // Route some documents containing the searchable attribute values given by the above array.
350         for (int i = 0; i < searchableAttributeValuesAsStrings.length; i++) {
351         	WorkflowDocument workflowDocument = WorkflowDocumentFactory.createDocument(principalId, documentTypeName);
352 
353         	// Add the string searchable attribute.
354         	WorkflowAttributeDefinition.Builder wcStringXMLDef = WorkflowAttributeDefinition.Builder.create("XMLSearchableAttributeWildcardString");
355         	wcStringXMLDef.addPropertyDefinition("xmlSearchableAttributeWildcardString", searchableAttributeValuesAsStrings[i][0]);
356         	workflowDocument.addSearchableDefinition(wcStringXMLDef.build());
357         	// Add the long searchable attribute.
358         	WorkflowAttributeDefinition.Builder wcLongXMLDef = WorkflowAttributeDefinition.Builder.create("XMLSearchableAttributeWildcardLong");
359         	wcLongXMLDef.addPropertyDefinition("xmlSearchableAttributeWildcardLong", searchableAttributeValuesAsStrings[i][1]);
360         	workflowDocument.addSearchableDefinition(wcLongXMLDef.build());
361         	// Add the float searchable attribute.
362         	WorkflowAttributeDefinition.Builder wcFloatXMLDef = WorkflowAttributeDefinition.Builder.create("XMLSearchableAttributeWildcardFloat");
363         	wcFloatXMLDef.addPropertyDefinition("xmlSearchableAttributeWildcardFloat", searchableAttributeValuesAsStrings[i][2]);
364         	workflowDocument.addSearchableDefinition(wcFloatXMLDef.build());
365         	// Add the datetime searchable attribute.
366         	WorkflowAttributeDefinition.Builder wcDatetimeXMLDef = WorkflowAttributeDefinition.Builder.create("XMLSearchableAttributeWildcardDatetime");
367         	wcDatetimeXMLDef.addPropertyDefinition("xmlSearchableAttributeWildcardDatetime", searchableAttributeValuesAsStrings[i][3]);
368         	workflowDocument.addSearchableDefinition(wcDatetimeXMLDef.build());
369 
370         	workflowDocument.setTitle("Search Def Test Doc " + i);
371         	workflowDocument.route("routing search def doc " + i);
372         }
373 
374         // Ensure that wildcards work on searchable string attributes. Note that this search should be case-insensitive by default.
375         // Also note that this should be the only case where the string-specific wildcards ("!", "?", and "*") should be working, unless
376         // they are being used in a range expression.
377         assertSearchableAttributeWildcardsWork(docType, principalId, "xmlSearchableAttributeWildcardString",
378         		new String[]  {"TESTSTRING|moretext", "!MoreText"   , "!anotherStr!testString", "!anotherStr&&!MoreText"  , "!SomeString"      ,
379         					"*str*"                 , "More????"    , "*e*n?"                 , "???String"               , "*te*&&!????String", "!test??????"       , "anotherStr..MoreText",
380         					"testString..MoreText"  , ">=testString", "<=anotherStr|>MoreText", "<=testString&&!anotherStr", ">=abc"             , "<=anotherOne&&>=text",
381         					">=More????"             , "<=*test*"},
382         			new int[] {2                    , 2             , 1                       , 1                         , 3                  ,
383         					2                       , 1             , 1                       , 0                         , 1                  , 2                   , 2 /*1*/               ,
384         					-1                       , 1             , 2                       , 2                         , 3                  , 0                   ,
385         					2                       , 2});
386 
387         // ensure multiple values work
388         assertSearchableAttributeMultiplesWork(docType, principalId, "xmlSearchableAttributeWildcardString",
389         		new String[][] { {"testString"}, {"anotherStr"}, {"MoreText"}, {"testString", "anotherStr"}, {"testString", "MoreText"}, {"anotherStr", "MoreText"}, {"testString", "anotherStr", "MoreText"}, {"monkey"}, {"monkey", "giraffe"}, {"monkey", "testString"} },
390         			new int[]  {  1,              1,              1,            2,                            2,                          2,                          3,                                        0,          0,                     1                       });
391 
392         // Ensure that wildcards work on searchable long attributes, and ensure the string-specific wildcards are not being utilized.
393         assertSearchableAttributeWildcardsWork(docType, principalId, "xmlSearchableAttributeWildcardLong",
394         		new String[]  {"99??", "*2"       , "!33"         , "<9984", ">=433", "<34", ">=432", ">=34&&<9984", "<100000&&>=20", ">=9984&&<33", "431..<9985",
395         					"9999..1", "<432|>432", ">=9000|<=100", "!", ">=-76"},
396         			new int[] {-1     , -1          , 2             , 2      , 1     , 1     , 2      , 1           , 3               , 0           , 2 /*1*/    ,
397         					-1        , 2          , 2             , -1 , 3});
398 
399         // ensure multiple values work
400         assertSearchableAttributeMultiplesWork(docType, principalId, "xmlSearchableAttributeWildcardLong",
401         		new String[][] { {"9984"}, {"33"}, {"432"}, {"9984", "33"}, {"9984", "432"}, {"33", "432"}, {"9984", "33", "432"}, {"7"}, {"7", "4488"}, {"7", "9984"} },
402         			new int[]  {  1,              1,              1,            2,                            2,                          2,                          3,                                        0,          0,                     1                       });
403 
404         // Ensure that wildcards work on searchable float attributes, and ensure the string-specific wildcards are not being utilized.
405         assertSearchableAttributeWildcardsWork(docType, principalId, "xmlSearchableAttributeWildcardFloat",
406         		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",
407         					"-50..<50"   , "100..10", "<38.1358|>=38.1357" , ">=123.4567|<0.11", "-1.1..<38.1357&&<3.3"},
408         			new int[] {-1        , 2        , -1                     , 2                , 2                     , 1         , 2          , 2         , 3           , 1                   , 0                       ,
409         					2           , -1        , 3                     , 2                , 1});
410 
411         // ensure multiple values work
412         assertSearchableAttributeMultiplesWork(docType, principalId, "xmlSearchableAttributeWildcardFloat",
413         		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"} },
414         			new int[]  {  1,              1,              1,            2,                            2,                          2,                          3,                                        0,          0,                     1                       });
415 
416 
417         // Ensure that wildcards work on searchable datetime attributes, and ensure the string-specific wildcards are not being utilized.
418         /* 06/24/2009, 07/08/2010, 12/12/2012 */
419         assertSearchableAttributeWildcardsWork(docType, principalId, "xmlSearchableAttributeWildcardDatetime",
420         		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",
421         					">=01/01/2001&&<06/24/2009", "11/29/1990..<12/31/2009"  , "12/13/2100..<08/09/1997",
422         					"<06/24/2009|>=12/12/2012" , "<06/25/2009|>=07/09/2010", ">02/31/2011"},
423         			new int[] {-1                      , -1                         , 2 /* supports NOT operator*/, 2            , 1            , 1             , 2             , 1                          ,
424         					0                          , 1                         , -1                       ,
425         					1                          , 2                         , -1});
426         
427         // ensure multiple values work
428         assertSearchableAttributeMultiplesWork(docType, principalId, "xmlSearchableAttributeWildcardDatetime",
429         		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"} },
430         			new int[]  {  1,              1,              1,            2,                            2,                          2,                          3,                                        0,          0,                     1                       });
431 
432     }
433 
434     /**
435      * A convenience method for testing wildcards on searchable attributes.
436      *
437      * @param docType The document type containing the attributes.
438      * @param principalId The ID of the user performing the search.
439      * @param fieldDefKey The name of the field given by the field definition on the searchable attribute.
440      * @param searchValues The wildcard-filled search strings to test.
441      * @param resultSizes The number of expected documents to be returned by the search; use -1 to indicate that an error should have occurred.
442      * @throws Exception
443      */
444     private void assertSearchableAttributeWildcardsWork(DocumentType docType, String principalId, String fieldDefKey, String[] searchValues,
445     		int[] resultSizes) throws Exception {
446     	DocumentSearchCriteria.Builder criteria = null;
447         DocumentSearchResults results = null;
448         DocumentSearchService docSearchService = KEWServiceLocator.getDocumentSearchService();
449         for (int i = 0; i < resultSizes.length; i++) {
450         	criteria = DocumentSearchCriteria.Builder.create();
451         	criteria.setDocumentTypeName(docType.getName());
452         	addSearchableAttribute(criteria, fieldDefKey, searchValues[i]);
453         	try {
454         		results = docSearchService.lookupDocuments(principalId, criteria.build());
455         		if (resultSizes[i] < 0) {
456         			fail(fieldDefKey + "'s search at loop index " + i + " should have thrown an exception");
457         		}
458         		if(resultSizes[i] != results.getSearchResults().size()){
459         			assertEquals(fieldDefKey + "'s search results at loop index " + i + " returned the wrong number of documents.", resultSizes[i], results.getSearchResults().size());
460         		}
461         	} catch (Exception ex) {
462         		if (resultSizes[i] >= 0) {
463                     ex.printStackTrace();
464         			fail(fieldDefKey + "'s search at loop index " + i + " should not have thrown an exception");
465         		}
466         	}
467         	GlobalVariables.clear();
468         }
469     }
470     
471     /**
472      * A convenience method for testing multiple value fields on searchable attributes.
473      *
474      * @param docType The document type containing the attributes.
475      * @param principalId The ID of the user performing the search.
476      * @param fieldDefKey The name of the field given by the field definition on the searchable attribute.
477      * @param searchValues The wildcard-filled search strings to test.
478      * @param resultSizes The number of expected documents to be returned by the search; use -1 to indicate that an error should have occurred.
479      * @throws Exception
480      */
481     private void assertSearchableAttributeMultiplesWork(DocumentType docType, String principalId, String fieldDefKey, String[][] searchValues,
482     		int[] resultSizes) throws Exception {
483         DocumentSearchCriteria.Builder criteria = null;
484         DocumentSearchResults results = null;
485         DocumentSearchService docSearchService = KEWServiceLocator.getDocumentSearchService();
486         for (int i = 0; i < resultSizes.length; i++) {
487             criteria = DocumentSearchCriteria.Builder.create();
488         	criteria.setDocumentTypeName(docType.getName());
489         	addSearchableAttribute(criteria, fieldDefKey, searchValues[i]);
490         	try {
491                 results = docSearchService.lookupDocuments(principalId, criteria.build());
492                 if (resultSizes[i] < 0) {
493         			fail(fieldDefKey + "'s search at loop index " + i + " should have thrown an exception");
494         		}
495         		if(resultSizes[i] != results.getSearchResults().size()){
496         			assertEquals(fieldDefKey + "'s search results at loop index " + i + " returned the wrong number of documents.", resultSizes[i], results.getSearchResults().size());
497         		}
498         	} catch (Exception ex) {
499         		if (resultSizes[i] >= 0) {
500         			fail(fieldDefKey + "'s search at loop index " + i + " should not have thrown an exception");
501         		}
502         	}
503         	GlobalVariables.clear();
504         }
505     }
506     
507     
508     
509     /**
510      * Per KULRICE-3681, tests that StandardGenericXMLSearchableAttribute throws no cast class exception when it shouldn't
511      */
512     @Test
513     public void testValidateUserSearchInputsNoCast() {
514     	StandardGenericXMLSearchableAttribute searchableAttribute = new StandardGenericXMLSearchableAttribute();
515         ExtensionDefinition extensionDefinition = KewApiServiceLocator.getExtensionRepositoryService().getExtensionByName("SearchableAttributeVisible");
516 
517         DocumentSearchCriteria.Builder criteria = DocumentSearchCriteria.Builder.create();
518     	Map<String, List<String>> simpleParamMap = new HashMap<String, List<String>>();
519     	simpleParamMap.put("givenname", Collections.singletonList("test"));
520         criteria.setDocumentAttributeValues(simpleParamMap);
521     	List errors = new ArrayList();
522     	Exception caughtException = null;
523     	try {
524     		errors = searchableAttribute.validateDocumentAttributeCriteria(extensionDefinition, criteria.build());
525     	} catch (RuntimeException re) {
526     		caughtException = re;
527     	}
528     	assertNull("Found exception "+caughtException, caughtException);
529     	assertTrue("Found errors "+errors, (errors.size() == 0));
530     	
531     	Map<String, List<String>> listParamMap = new HashMap<String, List<String>>();
532     	List<String> multipleValues = new ArrayList<String>();
533     	multipleValues.add("testone");
534     	multipleValues.add("testtwo");
535     	listParamMap.put("givenname", multipleValues);
536         criteria.setDocumentAttributeValues(listParamMap);
537     	errors = new ArrayList();
538     	caughtException = null;
539     	try {
540     		errors = searchableAttribute.validateDocumentAttributeCriteria(extensionDefinition, criteria.build());
541     	} catch (RuntimeException re) {
542     		caughtException = re;
543     	}
544     	assertNull("Found exception "+caughtException, caughtException);
545     	assertTrue("Found errors "+errors, (errors.size() == 0));
546     	
547     }
548     
549     @Test
550     public void testSearchableAttributeTrim() {
551     	RuleAttribute trimAttribute = KEWServiceLocator.getRuleAttributeService().findByName("TrimSearchableAttribute");
552     	assert(trimAttribute.getName().equals("TrimSearchableAttribute"));
553     	assert(trimAttribute.getResourceDescriptor().equals("org.kuali.rice.kew.docsearch.xml.StandardGenericXMLSearchableAttribute"));
554     	assert(trimAttribute.getLabel().equals("Unit111"));
555     	assert(trimAttribute.getType().equals("SearchableXmlAttribute"));
556     	assert(trimAttribute.getDescription().equals("Unit111"));
557     	assert(trimAttribute.getApplicationId().equals("NSTrimSearchableTest"));
558     	//System.out.println(trimAttribute.getName());
559     }
560 
561     @Test
562     public void testXmlGeneration() {
563         loadXmlFile("testdoc1.xml");
564         WorkflowAttributeDefinition searchableDefinition = WorkflowAttributeDefinition.Builder.create("SearchAttribute").build();
565         DocumentContentUpdate.Builder documentContentUpdateBuilder = DocumentContentUpdate.Builder.create();
566         documentContentUpdateBuilder.getSearchableDefinitions().add(searchableDefinition);
567         WorkflowDocument workflowDocument = WorkflowDocumentFactory.createDocument(getPrincipalId("ewestfal"), "SearchDoc", null, documentContentUpdateBuilder.build());
568         workflowDocument.route("");
569         assertTrue(workflowDocument.isFinal());
570         assertEquals(StringUtils.deleteWhitespace("<" + KewApiConstants.SEARCHABLE_CONTENT_ELEMENT + ">" + MockSearchableAttribute.SEARCH_CONTENT + "</" + KewApiConstants.SEARCHABLE_CONTENT_ELEMENT + ">"),
571                 StringUtils.deleteWhitespace(workflowDocument.getDocumentContent().getSearchableContent()));
572     }
573 
574     @Test
575     public void testAttributeRangeFieldGeneration() {
576         String documentTypeName = "SearchDocType";
577         DocumentType docType = ((DocumentTypeService)KEWServiceLocator.getService(KEWServiceLocator.DOCUMENT_TYPE_SERVICE)).findByName(documentTypeName);
578         String userNetworkId = "rkirkend";
579         WorkflowDocument workflowDocument = WorkflowDocumentFactory.createDocument(getPrincipalId(userNetworkId), documentTypeName);
580         workflowDocument.setTitle("Routing style");
581         workflowDocument.route("routing this document.");
582 
583         DocumentSearchCriteria.Builder criteria = DocumentSearchCriteria.Builder.create();
584         criteria.setDocumentTypeName(documentTypeName);
585         criteria.setDateApprovedFrom(new DateTime(2010, 1, 1, 0, 0));
586         criteria.setDateApprovedTo(new DateTime(2011, 1, 1, 0, 0));
587         String fieldValue = ">= " + DocumentSearchInternalUtils.getDisplayValueWithDateOnly(new Timestamp(TestXMLSearchableAttributeDateTime.SEARCH_STORAGE_VALUE_IN_MILLS));
588         addSearchableAttribute(criteria, TestXMLSearchableAttributeDateTime.SEARCH_STORAGE_KEY, fieldValue);
589 
590         Map<String, String[]> fields = new DocumentSearchCriteriaTranslatorImpl().translateCriteriaToFields(criteria.build());
591         System.err.println(fields);
592         String lowerBoundField = KewApiConstants.DOCUMENT_ATTRIBUTE_FIELD_PREFIX + KRADConstants.LOOKUP_RANGE_LOWER_BOUND_PROPERTY_PREFIX + TestXMLSearchableAttributeDateTime.SEARCH_STORAGE_KEY;
593         String upperBoundField = KewApiConstants.DOCUMENT_ATTRIBUTE_FIELD_PREFIX + TestXMLSearchableAttributeDateTime.SEARCH_STORAGE_KEY;
594 
595         assertNotNull(fields.get(lowerBoundField));
596         assertNotNull(fields.get(upperBoundField));
597         assertNotNull(fields.get(lowerBoundField)[0]);
598         assertNull(fields.get(upperBoundField)[0]);
599 
600         assertEquals(DocumentSearchInternalUtils.getDisplayValueWithDateOnly(new Timestamp(new DateTime(2007, 3, 15, 0, 0).toDateTime().getMillis())), fields.get(lowerBoundField)[0]);
601     }
602     
603 }