001    /**
002     * Copyright 2005-2011 The Kuali Foundation
003     *
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.opensource.org/licenses/ecl2.php
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.kuali.rice.kew.docsearch;
017    
018    import org.apache.commons.lang.StringUtils;
019    import org.joda.time.DateTime;
020    import org.junit.Test;
021    import org.kuali.rice.kew.api.KewApiConstants;
022    import org.kuali.rice.kew.api.KewApiServiceLocator;
023    import org.kuali.rice.kew.api.WorkflowDocument;
024    import org.kuali.rice.kew.api.WorkflowDocumentFactory;
025    import org.kuali.rice.kew.api.document.DocumentContentUpdate;
026    import org.kuali.rice.kew.api.document.attribute.WorkflowAttributeDefinition;
027    import org.kuali.rice.kew.api.document.search.DocumentSearchCriteria;
028    import org.kuali.rice.kew.api.document.search.DocumentSearchResults;
029    import org.kuali.rice.kew.api.extension.ExtensionDefinition;
030    import org.kuali.rice.kew.docsearch.service.DocumentSearchService;
031    import org.kuali.rice.kew.docsearch.xml.StandardGenericXMLSearchableAttribute;
032    import org.kuali.rice.kew.doctype.bo.DocumentType;
033    import org.kuali.rice.kew.doctype.service.DocumentTypeService;
034    import org.kuali.rice.kew.impl.document.search.DocumentSearchCriteriaTranslatorImpl;
035    import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue;
036    import org.kuali.rice.kew.routeheader.dao.DocumentRouteHeaderDAO;
037    import org.kuali.rice.kew.routeheader.service.RouteHeaderService;
038    import org.kuali.rice.kew.rule.bo.RuleAttribute;
039    import org.kuali.rice.kew.service.KEWServiceLocator;
040    import org.kuali.rice.kim.api.identity.Person;
041    import org.kuali.rice.kim.api.services.KimApiServiceLocator;
042    import org.kuali.rice.krad.util.GlobalVariables;
043    import org.kuali.rice.krad.util.KRADConstants;
044    
045    import java.math.BigDecimal;
046    import java.sql.Timestamp;
047    import java.util.ArrayList;
048    import java.util.Calendar;
049    import java.util.Collection;
050    import java.util.Collections;
051    import java.util.HashMap;
052    import java.util.List;
053    import java.util.Map;
054    
055    import static org.junit.Assert.*;
056    
057    /**
058     * Tests the StandardGenericXMLSearchableAttribute.
059     *
060     * KULWF-654: Tests the resolution to this issue by configuring a CustomActionListAttribute as well as a
061     * searchable attribute.
062     */
063    public class SearchableAttributeTest extends DocumentSearchTestBase {
064    
065        protected void loadTestData() throws Exception {
066            loadXmlFile("SearchAttributeConfig.xml");
067            loadXmlFile("SearchableTrimTest.xml");
068        }
069    
070    //    private SearchAttributeCriteriaComponent createSearchAttributeCriteriaComponent(String key,String value,Boolean isLowerBoundValue,DocumentType docType) {
071    //      String formKey = (isLowerBoundValue == null) ? key : ((isLowerBoundValue != null && isLowerBoundValue.booleanValue()) ? SearchableAttributeOld.RANGE_LOWER_BOUND_PROPERTY_PREFIX : SearchableAttributeOld.RANGE_UPPER_BOUND_PROPERTY_PREFIX);
072    //      String savedKey = key;
073    //      SearchAttributeCriteriaComponent sacc = new SearchAttributeCriteriaComponent(formKey,value,savedKey);
074    //      Field field = getFieldByFormKey(docType, formKey);
075    //      if (field != null) {
076    //              sacc.setSearchableAttributeValue(DocSearchUtils.getSearchableAttributeValueByDataTypeString(field.getFieldDataType()));
077    //              sacc.setRangeSearch(field.isMemberOfRange());
078    //              sacc.setAllowWildcards(field.isAllowingWildcards());
079    //              sacc.setAutoWildcardBeginning(field.isAutoWildcardAtBeginning());
080    //              sacc.setAutoWildcardEnd(field.isAutoWildcardAtEnding());
081    //              sacc.setCaseSensitive(field.isCaseSensitive());
082    //              sacc.setSearchInclusive(field.isInclusive());
083    //            sacc.setSearchable(field.isSearchable());
084    //            sacc.setCanHoldMultipleValues(Field.MULTI_VALUE_FIELD_TYPES.contains(field.getFieldType()));
085    //      }
086    //      return sacc;
087    //    }
088    //
089    //    private Field getFieldByFormKey(DocumentType docType, String formKey) {
090    //      if (docType == null) {
091    //              return null;
092    //      }
093    //              for (SearchableAttributeOld searchableAttribute : docType.getSearchableAttributesOld()) {
094    //                      for (Row row : searchableAttribute.getSearchingRows()) {
095    //                              for (Field field : row.getFields()) {
096    //                                      if (field.getPropertyName().equals(formKey)) {
097    //                                              return field;
098    //                                      }
099    //                              }
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    }