View Javadoc

1   /*
2    * Copyright 2005-2007 The Kuali Foundation
3    *
4    *
5    * Licensed under the Educational Community License, Version 2.0 (the "License");
6    * you may not use this file except in compliance with the License.
7    * You may obtain a copy of the License at
8    *
9    * http://www.opensource.org/licenses/ecl2.php
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.kuali.rice.kew.docsearch;
18  
19  import static org.junit.Assert.assertEquals;
20  import static org.junit.Assert.assertNotNull;
21  import static org.junit.Assert.assertNull;
22  import static org.junit.Assert.assertTrue;
23  import static org.junit.Assert.fail;
24  
25  import java.sql.Timestamp;
26  import java.util.ArrayList;
27  import java.util.Collections;
28  import java.util.HashMap;
29  import java.util.List;
30  import java.util.Map;
31  
32  import org.apache.commons.lang.StringUtils;
33  import org.junit.Test;
34  import org.kuali.rice.kew.api.KewApiServiceLocator;
35  import org.kuali.rice.kew.api.WorkflowDocument;
36  import org.kuali.rice.kew.api.WorkflowDocumentFactory;
37  import org.kuali.rice.kew.api.document.DocumentContentUpdate;
38  import org.kuali.rice.kew.api.document.attribute.WorkflowAttributeDefinition;
39  import org.kuali.rice.kew.api.extension.ExtensionDefinition;
40  import org.kuali.rice.kew.docsearch.service.DocumentSearchService;
41  import org.kuali.rice.kew.docsearch.xml.StandardGenericXMLSearchableAttribute;
42  import org.kuali.rice.kew.doctype.bo.DocumentType;
43  import org.kuali.rice.kew.doctype.service.DocumentTypeService;
44  import org.kuali.rice.kew.exception.WorkflowException;
45  import org.kuali.rice.kew.exception.WorkflowServiceErrorException;
46  import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue;
47  import org.kuali.rice.kew.routeheader.service.RouteHeaderService;
48  import org.kuali.rice.kew.rule.bo.RuleAttribute;
49  import org.kuali.rice.kew.rule.service.RuleAttributeService;
50  import org.kuali.rice.kew.service.KEWServiceLocator;
51  import org.kuali.rice.kew.util.KEWConstants;
52  import org.kuali.rice.kim.api.identity.Person;
53  import org.kuali.rice.kim.api.services.KimApiServiceLocator;
54  import org.kuali.rice.krad.util.GlobalVariables;
55  
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         /*
119         assertEquals("Wrong number of searchable attributes", 4, doc.getSearchableAttributeValues().size());
120 
121         for (Iterator<SearchableAttributeValue> iter = doc.getSearchableAttributeValues().iterator(); iter.hasNext();) {
122             SearchableAttributeValue attributeValue = iter.next();
123             if (attributeValue instanceof SearchableAttributeStringValue) {
124                 SearchableAttributeStringValue realValue = (SearchableAttributeStringValue) attributeValue;
125 
126                 for(String value:getRouteHeaderService().getSearchableAttributeStringValuesByKey(doc.getDocumentId(), realValue.getSearchableAttributeKey())){
127                 	assertEquals("Assert that the values are the same", value, attributeValue.getSearchableAttributeValue());
128                 }
129 
130             } else if (attributeValue instanceof SearchableAttributeLongValue) {
131                 SearchableAttributeLongValue realValue = (SearchableAttributeLongValue) attributeValue;
132                 for(Long value:getRouteHeaderService().getSearchableAttributeLongValuesByKey(doc.getDocumentId(), realValue.getSearchableAttributeKey())){
133                 	assertEquals("Assert that the values are the same", value, attributeValue.getSearchableAttributeValue());
134                 }
135             } else if (attributeValue instanceof SearchableAttributeFloatValue) {
136                 SearchableAttributeFloatValue realValue = (SearchableAttributeFloatValue) attributeValue;
137                 for(BigDecimal value:getRouteHeaderService().getSearchableAttributeFloatValuesByKey(doc.getDocumentId(), realValue.getSearchableAttributeKey())){
138                 	assertEquals("Assert that the values are the same", value, attributeValue.getSearchableAttributeValue());
139                 }
140 
141             } else if (attributeValue instanceof SearchableAttributeDateTimeValue) {
142                 SearchableAttributeDateTimeValue realValue = (SearchableAttributeDateTimeValue) attributeValue;
143                 assertEquals("The only DateTime attribute that should have been added has key '" + TestXMLSearchableAttributeDateTime.SEARCH_STORAGE_KEY + "'", TestXMLSearchableAttributeDateTime.SEARCH_STORAGE_KEY, realValue.getSearchableAttributeKey());
144 
145                 Calendar testDate = Calendar.getInstance();
146                 testDate.setTimeInMillis(realValue.getSearchableAttributeValue().getTime());
147                 testDate.set(Calendar.SECOND, 0);
148                 testDate.set(Calendar.MILLISECOND, 0);
149 
150                 for(Timestamp value:getRouteHeaderService().getSearchableAttributeDateTimeValuesByKey(doc.getDocumentId(), realValue.getSearchableAttributeKey())){
151                 	Calendar attributeDate = Calendar.getInstance();
152                     attributeDate.setTimeInMillis(value.getTime());
153                     attributeDate.set(Calendar.SECOND, 0);
154                     attributeDate.set(Calendar.MILLISECOND, 0);
155 
156                     assertEquals("The month value for the searchable attribute is wrong",testDate.get(Calendar.MONTH),attributeDate.get(Calendar.MONTH));
157                     assertEquals("The date value for the searchable attribute is wrong",testDate.get(Calendar.DATE),attributeDate.get(Calendar.DATE));
158                     assertEquals("The year value for the searchable attribute is wrong",testDate.get(Calendar.YEAR),attributeDate.get(Calendar.YEAR));
159                 }
160 
161             } else {
162                 fail("Searchable Attribute Value base class should be one of the four checked always");
163             }
164         }
165         */
166 
167     }
168 
169     protected RouteHeaderService getRouteHeaderService(){
170     	RouteHeaderService rRet = KEWServiceLocator.getRouteHeaderService();
171     	return rRet;
172     }
173 
174     protected String getPrincipalId(String networkId){
175     	return KimApiServiceLocator.getPersonService().getPersonByPrincipalName(networkId).getPrincipalId();
176     }
177 
178     @Test public void testCustomSearchableAttributesWithDataType() throws Exception {
179         String documentTypeName = "SearchDocType";
180     	DocumentType docType = ((DocumentTypeService)KEWServiceLocator.getService(KEWServiceLocator.DOCUMENT_TYPE_SERVICE)).findByName(documentTypeName);
181         String userNetworkId = "rkirkend";
182         WorkflowDocument workflowDocument = WorkflowDocumentFactory.createDocument(getPrincipalId(userNetworkId), documentTypeName);
183         workflowDocument.setTitle("Routing style");
184         workflowDocument.route("routing this document.");
185 
186         workflowDocument = WorkflowDocumentFactory.loadDocument(getPrincipalId(userNetworkId), workflowDocument.getDocumentId());
187         DocumentRouteHeaderValue doc = KEWServiceLocator.getRouteHeaderService().getRouteHeader(workflowDocument.getDocumentId());
188         /*assertEquals("Wrong number of searchable attributes", 4, doc.getSearchableAttributeValues().size());
189         for (Iterator<SearchableAttributeValue> iter = doc.getSearchableAttributeValues().iterator(); iter.hasNext();) {
190             SearchableAttributeValue attributeValue = iter.next();
191             if (attributeValue instanceof SearchableAttributeStringValue) {
192                 SearchableAttributeStringValue realValue = (SearchableAttributeStringValue) attributeValue;
193                 assertEquals("The only String attribute that should have been added has key '" + TestXMLSearchableAttributeString.SEARCH_STORAGE_KEY + "'", TestXMLSearchableAttributeString.SEARCH_STORAGE_KEY, realValue.getSearchableAttributeKey());
194                 assertEquals("The only String attribute that should have been added has value '" + TestXMLSearchableAttributeString.SEARCH_STORAGE_VALUE + "'", TestXMLSearchableAttributeString.SEARCH_STORAGE_VALUE, realValue.getSearchableAttributeValue());
195             } else if (attributeValue instanceof SearchableAttributeLongValue) {
196                 SearchableAttributeLongValue realValue = (SearchableAttributeLongValue) attributeValue;
197                 assertEquals("The only Long attribute that should have been added has key '" + TestXMLSearchableAttributeLong.SEARCH_STORAGE_KEY + "'", TestXMLSearchableAttributeLong.SEARCH_STORAGE_KEY, realValue.getSearchableAttributeKey());
198                 assertEquals("The only Long attribute that should have been added has value '" + TestXMLSearchableAttributeLong.SEARCH_STORAGE_VALUE + "'", TestXMLSearchableAttributeLong.SEARCH_STORAGE_VALUE, realValue.getSearchableAttributeValue());
199             } else if (attributeValue instanceof SearchableAttributeFloatValue) {
200                 SearchableAttributeFloatValue realValue = (SearchableAttributeFloatValue) attributeValue;
201                 assertEquals("The only Float attribute that should have been added has key '" + TestXMLSearchableAttributeFloat.SEARCH_STORAGE_KEY + "'", TestXMLSearchableAttributeFloat.SEARCH_STORAGE_KEY, realValue.getSearchableAttributeKey());
202                 assertTrue("The only Float attribute that should have been added has value '" + TestXMLSearchableAttributeFloat.SEARCH_STORAGE_VALUE + "'", 0 == TestXMLSearchableAttributeFloat.SEARCH_STORAGE_VALUE.compareTo(realValue.getSearchableAttributeValue()));
203             } else if (attributeValue instanceof SearchableAttributeDateTimeValue) {
204                 SearchableAttributeDateTimeValue realValue = (SearchableAttributeDateTimeValue) attributeValue;
205                 assertEquals("The only DateTime attribute that should have been added has key '" + TestXMLSearchableAttributeDateTime.SEARCH_STORAGE_KEY + "'", TestXMLSearchableAttributeDateTime.SEARCH_STORAGE_KEY, realValue.getSearchableAttributeKey());
206                 Calendar testDate = Calendar.getInstance();
207                 testDate.setTimeInMillis(TestXMLSearchableAttributeDateTime.SEARCH_STORAGE_VALUE_IN_MILLS);
208                 testDate.set(Calendar.SECOND, 0);
209                 testDate.set(Calendar.MILLISECOND, 0);
210                 Calendar attributeDate = Calendar.getInstance();
211                 attributeDate.setTimeInMillis(realValue.getSearchableAttributeValue().getTime());
212                 attributeDate.set(Calendar.SECOND, 0);
213                 attributeDate.set(Calendar.MILLISECOND, 0);
214                 assertEquals("The month value for the searchable attribute is wrong",testDate.get(Calendar.MONTH),attributeDate.get(Calendar.MONTH));
215                 assertEquals("The date value for the searchable attribute is wrong",testDate.get(Calendar.DATE),attributeDate.get(Calendar.DATE));
216                 assertEquals("The year value for the searchable attribute is wrong",testDate.get(Calendar.YEAR),attributeDate.get(Calendar.YEAR));
217             } else {
218                 fail("Searchable Attribute Value base class should be one of the four checked always");
219             }
220         }
221 		*/
222 
223         DocumentSearchService docSearchService = (DocumentSearchService) KEWServiceLocator.getService(KEWServiceLocator.DOCUMENT_SEARCH_SERVICE);
224         Person user = KimApiServiceLocator.getPersonService().getPersonByPrincipalName(userNetworkId);
225 
226 
227         DocSearchCriteriaDTO criteria = new DocSearchCriteriaDTO();
228         criteria.setDocTypeFullName(documentTypeName);
229         criteria.addSearchableAttribute(createSearchAttributeCriteriaComponent(TestXMLSearchableAttributeString.SEARCH_STORAGE_KEY, TestXMLSearchableAttributeString.SEARCH_STORAGE_VALUE, null, docType));
230         DocumentSearchResultComponents result = docSearchService.getList(user.getPrincipalId(), criteria);
231         List searchResults = result.getSearchResults();
232 
233         assertEquals("Search results should have one document.", 1, searchResults.size());
234 
235         criteria = new DocSearchCriteriaDTO();
236         criteria.setDocTypeFullName(documentTypeName);
237         criteria.addSearchableAttribute(createSearchAttributeCriteriaComponent(TestXMLSearchableAttributeString.SEARCH_STORAGE_KEY, "fred", null, docType));
238         result = docSearchService.getList(user.getPrincipalId(), criteria);
239         searchResults = result.getSearchResults();
240 
241         assertEquals("Search results should be empty.", 0, searchResults.size());
242 
243         criteria = new DocSearchCriteriaDTO();
244         criteria.setDocTypeFullName(documentTypeName);
245         criteria.addSearchableAttribute(createSearchAttributeCriteriaComponent("fakeproperty", "doesntexist", null, docType));
246         try {
247             result = docSearchService.getList(user.getPrincipalId(), criteria);
248             fail("Search results should be throwing a validation exception for use of non-existant searchable attribute");
249         } catch (WorkflowServiceErrorException e) {}
250 
251         criteria = new DocSearchCriteriaDTO();
252         criteria.setDocTypeFullName(documentTypeName);
253         criteria.addSearchableAttribute(createSearchAttributeCriteriaComponent(TestXMLSearchableAttributeLong.SEARCH_STORAGE_KEY, TestXMLSearchableAttributeLong.SEARCH_STORAGE_VALUE.toString(), null, docType));
254         result = docSearchService.getList(user.getPrincipalId(), criteria);
255         searchResults = result.getSearchResults();
256         assertEquals("Search results should have one document.", 1, searchResults.size());
257 
258         criteria = new DocSearchCriteriaDTO();
259         criteria.setDocTypeFullName(documentTypeName);
260         criteria.addSearchableAttribute(createSearchAttributeCriteriaComponent(TestXMLSearchableAttributeLong.SEARCH_STORAGE_KEY, "1111111", null, docType));
261         result = docSearchService.getList(user.getPrincipalId(), criteria);
262         searchResults = result.getSearchResults();
263         assertEquals("Search results should be empty.", 0, searchResults.size());
264 
265         criteria = new DocSearchCriteriaDTO();
266         criteria.setDocTypeFullName(documentTypeName);
267         criteria.addSearchableAttribute(createSearchAttributeCriteriaComponent("fakeymcfakefake", "99999999", null, docType));
268         try {
269             result = docSearchService.getList(user.getPrincipalId(), criteria);
270             fail("Search results should be throwing a validation exception for use of non-existant searchable attribute");
271         } catch (WorkflowServiceErrorException e) {}
272 
273         criteria = new DocSearchCriteriaDTO();
274         criteria.setDocTypeFullName(documentTypeName);
275         criteria.addSearchableAttribute(createSearchAttributeCriteriaComponent(TestXMLSearchableAttributeFloat.SEARCH_STORAGE_KEY, TestXMLSearchableAttributeFloat.SEARCH_STORAGE_VALUE.toString(), null, docType));
276         result = docSearchService.getList(user.getPrincipalId(), criteria);
277         searchResults = result.getSearchResults();
278         assertEquals("Search results should have one document.", 1, searchResults.size());
279 
280         criteria = new DocSearchCriteriaDTO();
281         criteria.setDocTypeFullName(documentTypeName);
282         criteria.addSearchableAttribute(createSearchAttributeCriteriaComponent(TestXMLSearchableAttributeFloat.SEARCH_STORAGE_KEY, "215.3548", null, docType));
283         result = docSearchService.getList(user.getPrincipalId(), criteria);
284         searchResults = result.getSearchResults();
285         assertEquals("Search results should be empty.", 0, searchResults.size());
286 
287         criteria = new DocSearchCriteriaDTO();
288         criteria.setDocTypeFullName(documentTypeName);
289         criteria.addSearchableAttribute(createSearchAttributeCriteriaComponent("fakeylostington", "9999.9999", null, docType));
290         try {
291             result = docSearchService.getList(user.getPrincipalId(), criteria);
292             fail("Search results should be throwing a validation exception for use of non-existant searchable attribute");
293         } catch (WorkflowServiceErrorException e) {}
294 
295         criteria = new DocSearchCriteriaDTO();
296         criteria.setDocTypeFullName(documentTypeName);
297         criteria.addSearchableAttribute(createSearchAttributeCriteriaComponent(TestXMLSearchableAttributeDateTime.SEARCH_STORAGE_KEY, DocSearchUtils.getDisplayValueWithDateOnly(new Timestamp(TestXMLSearchableAttributeDateTime.SEARCH_STORAGE_VALUE_IN_MILLS)), null, docType));
298         result = docSearchService.getList(user.getPrincipalId(), criteria);
299         searchResults = result.getSearchResults();
300         assertEquals("Search results should have one document.", 1, searchResults.size());
301 
302         criteria = new DocSearchCriteriaDTO();
303         criteria.setDocTypeFullName(documentTypeName);
304         criteria.addSearchableAttribute(createSearchAttributeCriteriaComponent(TestXMLSearchableAttributeDateTime.SEARCH_STORAGE_KEY, "07/06/1979", null, docType));
305         result = docSearchService.getList(user.getPrincipalId(), criteria);
306         searchResults = result.getSearchResults();
307         assertEquals("Search results should be empty.", 0, searchResults.size());
308 
309         criteria = new DocSearchCriteriaDTO();
310         criteria.setDocTypeFullName(documentTypeName);
311         criteria.addSearchableAttribute(createSearchAttributeCriteriaComponent("lastingsfakerson", "07/06/2007", null, docType));
312         try {
313             result = docSearchService.getList(user.getPrincipalId(), criteria);
314             fail("Search results should be throwing a validation exception for use of non-existant searchable attribute");
315         } catch (WorkflowServiceErrorException e) {}
316     }
317 
318     /**
319      * Tests searching documents with searchable attributes
320      * @throws WorkflowException
321      */
322     @Test public void testSearchAttributesAcrossDocumentTypeVersions() throws Exception {
323         // first test searching for an initial version of the doc which does not have a searchable attribute
324         loadXmlFile("testdoc0.xml");
325 
326         String documentTypeName = "SearchDoc";
327         WorkflowDocument doc = WorkflowDocumentFactory.createDocument(getPrincipalIdForName("arh14"), documentTypeName);
328         DocumentType docType = ((DocumentTypeService)KEWServiceLocator.getService(KEWServiceLocator.DOCUMENT_TYPE_SERVICE)).findByName(documentTypeName);
329         doc.route("routing");
330 
331         DocumentSearchService docSearchService = (DocumentSearchService) KEWServiceLocator.getService(KEWServiceLocator.DOCUMENT_SEARCH_SERVICE);
332 
333         DocSearchCriteriaDTO criteria = new DocSearchCriteriaDTO();
334         criteria.setDocTypeFullName(documentTypeName);
335         criteria.setFromDateCreated("01/01/2004");
336 
337         Person user = KimApiServiceLocator.getPersonService().getPersonByPrincipalName("arh14");
338         DocumentSearchResultComponents result = docSearchService.getList(user.getPrincipalId(), criteria);
339         assertEquals(1, result.getSearchResults().size());
340 
341         // now upload the new version with a searchable attribute
342         loadXmlFile("testdoc1.xml");
343         docType = ((DocumentTypeService)KEWServiceLocator.getService(KEWServiceLocator.DOCUMENT_TYPE_SERVICE)).findByName(documentTypeName);
344 
345         // route a new doc
346         doc = WorkflowDocumentFactory.createDocument(getPrincipalIdForName("arh14"), documentTypeName);
347         doc.route("routing");
348 
349         // with no attribute criteria, both docs should be found
350         criteria = new DocSearchCriteriaDTO();
351         criteria.setDocTypeFullName(documentTypeName);
352         criteria.setFromDateCreated("01/01/2004");
353 
354         result = docSearchService.getList(user.getPrincipalId(), criteria);
355         assertEquals(2, result.getSearchResults().size());
356 
357         // search with specific SearchableAttributeOld value
358         criteria = new DocSearchCriteriaDTO();
359         criteria.setDocTypeFullName(documentTypeName);
360         criteria.setFromDateCreated("01/01/2004");
361         criteria.addSearchableAttribute(createSearchAttributeCriteriaComponent("MockSearchableAttributeKey", "Mock Searchable Attribute", null, docType));
362         criteria.getSearchableAttributes().set(0, createSearchAttributeCriteriaComponent("MockSearchableAttributeKey", "MockSearchableAttributeValue", null, docType));
363 
364         result = docSearchService.getList(user.getPrincipalId(), criteria);
365         assertEquals(1, result.getSearchResults().size());
366 
367         // search with any SearchableAttributeOld value
368         criteria = new DocSearchCriteriaDTO();
369         criteria.setDocTypeFullName(documentTypeName);
370         criteria.setFromDateCreated("01/01/2004");
371         criteria.addSearchableAttribute(createSearchAttributeCriteriaComponent("MockSearchableAttributeKey", "Mock Searchable Attribute", null, docType));
372         criteria.getSearchableAttributes().set(0, createSearchAttributeCriteriaComponent("MockSearchableAttributeKey", "", null, docType));
373 
374         result = docSearchService.getList(user.getPrincipalId(), criteria);
375         // should return two because an empty value above will return any value of the 'MockSearchableAttributeKey' key including the previous document
376         // that doesn't even have a record of that field being saved to the database
377         assertEquals(2, result.getSearchResults().size());
378     }
379 
380     /**
381      * Tests the usage of wildcards on searchable attributes of varying data types.
382      * Note that the bounds of ".."-related search expressions will not throw an exception if the lower bound is greater than the upper bound;
383      * instead, such an expression will simply return zero results.
384      * @throws Exception
385      */
386     @Test public void testWildcardsOnSearchableAttributes() throws Exception {
387         String documentTypeName = "WildcardTestDocType";
388     	DocumentType docType = KEWServiceLocator.getDocumentTypeService().findByName(documentTypeName);
389         String principalName = "rkirkend";
390         String principalId = KimApiServiceLocator.getPersonService().getPersonByPrincipalName(principalName).getPrincipalId();
391         String[][] searchableAttributeValuesAsStrings = { {"testString", "9984", "38.1357", "06/24/2009"},
392         		{"anotherStr", "33", "80000.65432", "07/08/2010"}, {"MoreText", "432", "-0.765", "12/12/2012"} };
393 
394         // Route some documents containing the searchable attribute values given by the above array.
395         for (int i = 0; i < searchableAttributeValuesAsStrings.length; i++) {
396         	WorkflowDocument workflowDocument = WorkflowDocumentFactory.createDocument(principalId, documentTypeName);
397 
398         	// Add the string searchable attribute.
399         	WorkflowAttributeDefinition.Builder wcStringXMLDef = WorkflowAttributeDefinition.Builder.create("XMLSearchableAttributeWildcardString");
400         	wcStringXMLDef.addPropertyDefinition("xmlSearchableAttributeWildcardString", searchableAttributeValuesAsStrings[i][0]);
401         	workflowDocument.addSearchableDefinition(wcStringXMLDef.build());
402         	// Add the long searchable attribute.
403         	WorkflowAttributeDefinition.Builder wcLongXMLDef = WorkflowAttributeDefinition.Builder.create("XMLSearchableAttributeWildcardLong");
404         	wcLongXMLDef.addPropertyDefinition("xmlSearchableAttributeWildcardLong", searchableAttributeValuesAsStrings[i][1]);
405         	workflowDocument.addSearchableDefinition(wcLongXMLDef.build());
406         	// Add the float searchable attribute.
407         	WorkflowAttributeDefinition.Builder wcFloatXMLDef = WorkflowAttributeDefinition.Builder.create("XMLSearchableAttributeWildcardFloat");
408         	wcFloatXMLDef.addPropertyDefinition("xmlSearchableAttributeWildcardFloat", searchableAttributeValuesAsStrings[i][2]);
409         	workflowDocument.addSearchableDefinition(wcFloatXMLDef.build());
410         	// Add the datetime searchable attribute.
411         	WorkflowAttributeDefinition.Builder wcDatetimeXMLDef = WorkflowAttributeDefinition.Builder.create("XMLSearchableAttributeWildcardDatetime");
412         	wcDatetimeXMLDef.addPropertyDefinition("xmlSearchableAttributeWildcardDatetime", searchableAttributeValuesAsStrings[i][3]);
413         	workflowDocument.addSearchableDefinition(wcDatetimeXMLDef.build());
414 
415         	workflowDocument.setTitle("Search Def Test Doc " + i);
416         	workflowDocument.route("routing search def doc " + i);
417         }
418 
419         // Ensure that wildcards work on searchable string attributes. Note that this search should be case-insensitive by default.
420         // Also note that this should be the only case where the string-specific wildcards ("!", "?", and "*") should be working, unless
421         // they are being used in a range expression.
422         assertSearchableAttributeWildcardsWork(docType, principalId, "xmlSearchableAttributeWildcardString",
423         		new String[]  {"TESTSTRING|moretext", "!MoreText"   , "!anotherStr!testString", "!anotherStr&&!MoreText"  , "!SomeString"      ,
424         					"*str*"                 , "More????"    , "*e*n?"                 , "???String"               , "*te*&&!????String", "!test??????"       , "anotherStr..MoreText",
425         					"testString..MoreText"  , ">=testString", "<=anotherStr|>MoreText", "<testString&&!anotherStr", ">abc"             , "<anotherOne&&>text",
426         					">More????"             , "<*test*"},
427         			new int[] {2                    , 2             , 1                       , 1                         , 3                  ,
428         					2                       , 1             , 1                       , 0                         , 1                  , 2                   , 2 /*1*/               ,
429         					0                       , 1             , 2                       , 1                         , 3                  , 0                   ,
430         					2                       , 2});
431         
432         // ensure multiple values work
433         assertSearchableAttributeMultiplesWork(docType, principalId, "xmlSearchableAttributeWildcardString",
434         		new String[][] { {"testString"}, {"anotherStr"}, {"MoreText"}, {"testString", "anotherStr"}, {"testString", "MoreText"}, {"anotherStr", "MoreText"}, {"testString", "anotherStr", "MoreText"}, {"monkey"}, {"monkey", "giraffe"}, {"monkey", "testString"} },
435         			new int[]  {  1,              1,              1,            2,                            2,                          2,                          3,                                        0,          0,                     1                       });
436 
437         // Ensure that wildcards work on searchable long attributes, and ensure the string-specific wildcards are not being utilized.
438         assertSearchableAttributeWildcardsWork(docType, principalId, "xmlSearchableAttributeWildcardLong",
439         		new String[]  {"99??", "*2"       , "!33"         , "<9984", ">432", "<=33", ">=432", ">33&&<9984", "<=100000&&>=20", ">9984&&<33", "432..9984",
440         					"9999..1", "<432|>432", ">=9000|<=100", "!", ">-76"},
441         			new int[] {-1     , -1          , 1             , 2      , 1     , 1     , 2      , 1           , 3               , 0           , 2 /*1*/    ,
442         					0        , 2          , 2             , -1 , 3});
443         
444         // ensure multiple values work
445         assertSearchableAttributeMultiplesWork(docType, principalId, "xmlSearchableAttributeWildcardLong",
446         		new String[][] { {"9984"}, {"33"}, {"432"}, {"9984", "33"}, {"9984", "432"}, {"33", "432"}, {"9984", "33", "432"}, {"7"}, {"7", "4488"}, {"7", "9984"} },
447         			new int[]  {  1,              1,              1,            2,                            2,                          2,                          3,                                        0,          0,                     1                       });
448 
449         // Ensure that wildcards work on searchable float attributes, and ensure the string-specific wildcards are not being utilized.
450         assertSearchableAttributeWildcardsWork(docType, principalId, "xmlSearchableAttributeWildcardFloat",
451         		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",
452         					"-50..50"   , "100..10", "<=38.1357|>=38.1357" , ">123.4567|<0.11", "-1.1..38.1357&&<3.3"},
453         			new int[] {-1        , 1        , -1                     , 2                , 2                     , 1         , 2          , 2         , 3           , 1                   , 0                       ,
454         					2           , 0        , 3                     , 2                , 1});
455         
456         // ensure multiple values work
457         assertSearchableAttributeMultiplesWork(docType, principalId, "xmlSearchableAttributeWildcardFloat",
458         		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"} },
459         			new int[]  {  1,              1,              1,            2,                            2,                          2,                          3,                                        0,          0,                     1                       });
460 
461 
462         // Ensure that wildcards work on searchable datetime attributes, and ensure the string-specific wildcards are not being utilized.
463         /* 06/24/2009, 07/08/2010, 12/12/2012 */
464         assertSearchableAttributeWildcardsWork(docType, principalId, "xmlSearchableAttributeWildcardDatetime",
465         		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",
466         					">=01/01/2001&&<06/24/2009", "11/29/1990..12/31/2009"  , "12/13/2100..08/09/1997",
467         					"<06/24/2009|>=12/12/2012" , "<=06/24/2009|>07/08/2010", ">02/31/2011"},
468         			new int[] {-1                      , -1                         , -1                      , 2            , 1            , 1             , 2             , 1                          ,
469         					0                          , 1                         , 0                       ,
470         					1                          , 2                         , -1});
471         
472         // ensure multiple values work
473         assertSearchableAttributeMultiplesWork(docType, principalId, "xmlSearchableAttributeWildcardDatetime",
474         		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"} },
475         			new int[]  {  1,              1,              1,            2,                            2,                          2,                          3,                                        0,          0,                     1                       });
476 
477     }
478 
479     /**
480      * A convenience method for testing wildcards on searchable attributes.
481      *
482      * @param docType The document type containing the attributes.
483      * @param principalId The ID of the user performing the search.
484      * @param fieldDefKey The name of the field given by the field definition on the searchable attribute.
485      * @param searchValues The wildcard-filled search strings to test.
486      * @param resultSizes The number of expected documents to be returned by the search; use -1 to indicate that an error should have occurred.
487      * @throws Exception
488      */
489     private void assertSearchableAttributeWildcardsWork(DocumentType docType, String principalId, String fieldDefKey, String[] searchValues,
490     		int[] resultSizes) throws Exception {
491     	DocSearchCriteriaDTO criteria = null;
492         DocumentSearchResultComponents result = null;
493         List<DocumentSearchResult> searchResults = null;
494         DocumentSearchService docSearchService = KEWServiceLocator.getDocumentSearchService();
495         for (int i = 0; i < resultSizes.length; i++) {
496         	criteria = new DocSearchCriteriaDTO();
497         	criteria.setDocTypeFullName(docType.getName());
498         	criteria.addSearchableAttribute(this.createSearchAttributeCriteriaComponent(fieldDefKey, searchValues[i], docType));
499         	try {
500         		result = docSearchService.getList(principalId, criteria);
501         		searchResults = result.getSearchResults();
502         		if (resultSizes[i] < 0) {
503         			fail(fieldDefKey + "'s search at loop index " + i + " should have thrown an exception");
504         		}
505         		if(resultSizes[i] != searchResults.size()){
506         			assertEquals(fieldDefKey + "'s search results at loop index " + i + " returned the wrong number of documents.", resultSizes[i], searchResults.size());
507         		}
508         	} catch (Exception ex) {
509         		if (resultSizes[i] >= 0) {
510         			fail(fieldDefKey + "'s search at loop index " + i + " should not have thrown an exception");
511         		}
512         	}
513         	GlobalVariables.clear();
514         }
515     }
516     
517     /**
518      * A convenience method for testing multiple value fields on searchable attributes.
519      *
520      * @param docType The document type containing the attributes.
521      * @param principalId The ID of the user performing the search.
522      * @param fieldDefKey The name of the field given by the field definition on the searchable attribute.
523      * @param searchValues The wildcard-filled search strings to test.
524      * @param resultSizes The number of expected documents to be returned by the search; use -1 to indicate that an error should have occurred.
525      * @throws Exception
526      */
527     private void assertSearchableAttributeMultiplesWork(DocumentType docType, String principalId, String fieldDefKey, String[][] searchValues,
528     		int[] resultSizes) throws Exception {
529     	DocSearchCriteriaDTO criteria = null;
530         DocumentSearchResultComponents result = null;
531         List<DocumentSearchResult> searchResults = null;
532         DocumentSearchService docSearchService = KEWServiceLocator.getDocumentSearchService();
533         for (int i = 0; i < resultSizes.length; i++) {
534         	criteria = new DocSearchCriteriaDTO();
535         	criteria.setDocTypeFullName(docType.getName());
536         	criteria.addSearchableAttribute(this.createSearchAttributeCriteriaComponent(fieldDefKey, searchValues[i], docType));
537         	try {
538         		result = docSearchService.getList(principalId, criteria);
539         		searchResults = result.getSearchResults();
540         		if (resultSizes[i] < 0) {
541         			fail(fieldDefKey + "'s search at loop index " + i + " should have thrown an exception");
542         		}
543         		if(resultSizes[i] != searchResults.size()){
544         			assertEquals(fieldDefKey + "'s search results at loop index " + i + " returned the wrong number of documents.", resultSizes[i], searchResults.size());
545         		}
546         	} catch (Exception ex) {
547         		if (resultSizes[i] >= 0) {
548         			fail(fieldDefKey + "'s search at loop index " + i + " should not have thrown an exception");
549         		}
550         	}
551         	GlobalVariables.clear();
552         }
553     }
554     
555     
556     
557     /**
558      * Per KULRICE-3681, tests that StandardGenericXMLSearchableAttribute throws no cast class exception when it shouldn't
559      */
560     @Test
561     public void testValidateUserSearchInputsNoCast() {
562     	StandardGenericXMLSearchableAttribute searchableAttribute = new StandardGenericXMLSearchableAttribute();
563     	final RuleAttributeService ruleAttributeService = KEWServiceLocator.getRuleAttributeService();
564         ExtensionDefinition extensionDefinition = KewApiServiceLocator.getExtensionRepositoryService().getExtensionByName("SearchableAttributeVisible");
565     	
566     	Map<String, List<String>> simpleParamMap = new HashMap<String, List<String>>();
567     	simpleParamMap.put("givenname", Collections.singletonList("test"));
568     	List errors = new ArrayList();
569     	Exception caughtException = null;
570     	try {
571     		errors = searchableAttribute.validateSearchFieldParameters(extensionDefinition, simpleParamMap, null);
572     	} catch (RuntimeException re) {
573     		caughtException = re;
574     	}
575     	assertNull("Found exception "+caughtException, caughtException);
576     	assertTrue("Found errors "+errors, (errors.size() == 0));
577     	
578     	Map<String, List<String>> listParamMap = new HashMap<String, List<String>>();
579     	List<String> multipleValues = new ArrayList<String>();
580     	multipleValues.add("testone");
581     	multipleValues.add("testtwo");
582     	listParamMap.put("givenname", multipleValues);
583     	errors = new ArrayList();
584     	caughtException = null;
585     	try {
586     		errors = searchableAttribute.validateSearchFieldParameters(extensionDefinition, listParamMap, null);
587     	} catch (RuntimeException re) {
588     		caughtException = re;
589     	}
590     	assertNull("Found exception "+caughtException, caughtException);
591     	assertTrue("Found errors "+errors, (errors.size() == 0));
592     	
593     }
594     
595     @Test
596     public void testSearchableAttributeTrim() {
597     	RuleAttribute trimAttribute = KEWServiceLocator.getRuleAttributeService().findByName("TrimSearchableAttribute");
598     	assert(trimAttribute.getName().equals("TrimSearchableAttribute"));
599     	assert(trimAttribute.getClassName().equals("org.kuali.rice.kew.docsearch.xml.StandardGenericXMLSearchableAttribute"));
600     	assert(trimAttribute.getLabel().equals("Unit111"));
601     	assert(trimAttribute.getType().equals("SearchableXmlAttribute"));
602     	assert(trimAttribute.getDescription().equals("Unit111"));
603     	assert(trimAttribute.getApplicationId().equals("NSTrimSearchableTest"));
604     	//System.out.println(trimAttribute.getName());
605     }
606 
607     @Test
608     public void testXmlGeneration() {
609         loadXmlFile("testdoc1.xml");
610         WorkflowAttributeDefinition searchableDefinition = WorkflowAttributeDefinition.Builder.create("SearchAttribute").build();
611         DocumentContentUpdate.Builder documentContentUpdateBuilder = DocumentContentUpdate.Builder.create();
612         documentContentUpdateBuilder.getSearchableDefinitions().add(searchableDefinition);
613         WorkflowDocument workflowDocument = WorkflowDocumentFactory.createDocument(getPrincipalId("ewestfal"), "SearchDoc", null, documentContentUpdateBuilder.build());
614         workflowDocument.route("");
615         assertTrue(workflowDocument.isFinal());
616         assertEquals(StringUtils.deleteWhitespace("<" + KEWConstants.SEARCHABLE_CONTENT_ELEMENT + ">" + MockSearchableAttribute.SEARCH_CONTENT + "</" + KEWConstants.SEARCHABLE_CONTENT_ELEMENT + ">"),
617                 StringUtils.deleteWhitespace(workflowDocument.getDocumentContent().getSearchableContent()));
618     }
619     
620 }