View Javadoc
1   /**
2    * Copyright 2005-2014 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.xml;
17  
18  import org.joda.time.DateMidnight;
19  import org.joda.time.DateTime;
20  import org.junit.Ignore;
21  import org.junit.Test;
22  import org.kuali.rice.core.api.uif.RemotableAttributeError;
23  import org.kuali.rice.core.api.uif.RemotableAttributeField;
24  import org.kuali.rice.core.api.util.RiceConstants;
25  import org.kuali.rice.kew.api.WorkflowDocument;
26  import org.kuali.rice.kew.api.WorkflowDocumentFactory;
27  import org.kuali.rice.kew.api.document.Document;
28  import org.kuali.rice.kew.api.document.DocumentContent;
29  import org.kuali.rice.kew.api.document.DocumentWithContent;
30  import org.kuali.rice.kew.api.document.attribute.DocumentAttribute;
31  import org.kuali.rice.kew.api.document.attribute.WorkflowAttributeDefinition;
32  import org.kuali.rice.kew.api.document.search.DocumentSearchCriteria;
33  import org.kuali.rice.kew.api.document.search.DocumentSearchResult;
34  import org.kuali.rice.kew.api.document.search.DocumentSearchResults;
35  import org.kuali.rice.kew.api.exception.WorkflowException;
36  import org.kuali.rice.kew.api.extension.ExtensionDefinition;
37  import org.kuali.rice.kew.docsearch.DocumentSearchInternalUtils;
38  import org.kuali.rice.kew.docsearch.DocumentSearchTestBase;
39  import org.kuali.rice.kew.docsearch.SearchableAttributeLongValue;
40  import org.kuali.rice.kew.docsearch.TestXMLSearchableAttributeDateTime;
41  import org.kuali.rice.kew.docsearch.TestXMLSearchableAttributeFloat;
42  import org.kuali.rice.kew.docsearch.TestXMLSearchableAttributeLong;
43  import org.kuali.rice.kew.docsearch.TestXMLSearchableAttributeString;
44  import org.kuali.rice.kew.docsearch.service.DocumentSearchService;
45  import org.kuali.rice.kew.doctype.bo.DocumentType;
46  import org.kuali.rice.kew.doctype.service.DocumentTypeService;
47  import org.kuali.rice.kew.exception.WorkflowServiceErrorException;
48  import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue;
49  import org.kuali.rice.kew.service.KEWServiceLocator;
50  import org.kuali.rice.kew.test.TestUtilities;
51  import org.kuali.rice.kim.api.identity.Person;
52  import org.kuali.rice.kim.api.services.KimApiServiceLocator;
53  import org.kuali.rice.kns.util.FieldUtils;
54  import org.kuali.rice.kns.web.ui.Field;
55  import org.kuali.rice.kns.web.ui.Row;
56  import org.kuali.rice.test.BaselineTestCase;
57  import org.w3c.dom.Element;
58  import org.xml.sax.InputSource;
59  
60  import javax.xml.parsers.DocumentBuilderFactory;
61  import javax.xml.xpath.XPath;
62  import javax.xml.xpath.XPathConstants;
63  import javax.xml.xpath.XPathFactory;
64  import java.io.BufferedReader;
65  import java.io.StringReader;
66  import java.math.BigDecimal;
67  import java.math.BigInteger;
68  import java.sql.Timestamp;
69  import java.text.ParseException;
70  import java.util.Date;
71  import java.util.List;
72  
73  import static org.junit.Assert.*;
74  import static org.junit.Assert.assertEquals;
75  
76  /**
77   * Tests the StandardGenericXMLSearchableAttribute.
78   *
79   * KULWF-654: Tests the resolution to this issue by configuring a CustomActionListAttribute as well as a
80   * searchable attribute.
81   */
82  @BaselineTestCase.BaselineMode(BaselineTestCase.Mode.NONE)
83  public class StandardGenericXMLSearchableAttributeTest extends DocumentSearchTestBase {
84  
85      protected void loadTestData() throws Exception {
86          loadXmlFile("XmlConfig.xml");
87      }
88  
89      @Test public void testXMLStandardSearchableAttributeWithInvalidValue() throws Exception {
90          String documentTypeName = "SearchDocTypeStandardSearchDataType";
91          String userNetworkId = "rkirkend";
92          WorkflowDocument workflowDocument = WorkflowDocumentFactory.createDocument(getPrincipalIdForName(userNetworkId), documentTypeName);
93  
94          /*
95           *   Below we are using the keys and values from the custom searchable attribute classes' static constants but
96           *   this is only for convenience as those should always be valid values to test for.
97           */
98          // adding string value in what should be a long searchable attribute
99          WorkflowAttributeDefinition.Builder longXMLDef = WorkflowAttributeDefinition.Builder.create("XMLSearchableAttributeStdLong");
100         longXMLDef.addPropertyDefinition(TestXMLSearchableAttributeLong.SEARCH_STORAGE_KEY, "123x23");
101         workflowDocument.addSearchableDefinition(longXMLDef.build());
102 
103         workflowDocument.setTitle("Routing style");
104         try {
105             workflowDocument.route("routing this document.");
106             fail("Document should be unroutable with invalid searchable attribute value");
107         } catch (Exception e) {
108             e.printStackTrace();
109         }
110         /*
111          * The call to TestUtilities below is needed because when exception routing spawns a new thread (see
112          * TestExceptionRoutingServiceImpl class) the next test will begin before the exception thread is complete and
113          * cause errors. This was originally discovered because the test method
114          * testXMLStandardSearchableAttributesWithDataType() would run and get errors loading xml data for workgroups
115          * perhaps because the exception thread was keeping the cache around and now allowing it to be cleared?
116          */
117         TestUtilities.waitForExceptionRouting();
118     }
119 
120     @Test public void testXMLStandardSearchableAttributesWithDataType() throws Exception {
121         String documentTypeName = "SearchDocTypeStandardSearchDataType";
122         DocumentType docType = ((DocumentTypeService)KEWServiceLocator.getService(KEWServiceLocator.DOCUMENT_TYPE_SERVICE)).findByName(documentTypeName);
123         String userNetworkId = "rkirkend";
124         WorkflowDocument workflowDocument = WorkflowDocumentFactory.createDocument(getPrincipalIdForName(userNetworkId), documentTypeName);
125 
126         /*
127          *   Below we are using the keys and values from the custom searchable attribute classes' static constants but
128          *   this is only for convenience as those should always be valid values to test for.
129          */
130         int i = 0;
131         // adding string searchable attribute
132         i++;
133         WorkflowAttributeDefinition.Builder stringXMLDef = WorkflowAttributeDefinition.Builder.create("XMLSearchableAttribute");
134         stringXMLDef.addPropertyDefinition(TestXMLSearchableAttributeString.SEARCH_STORAGE_KEY, TestXMLSearchableAttributeString.SEARCH_STORAGE_VALUE);
135         workflowDocument.addSearchableDefinition(stringXMLDef.build());
136         // adding long searchable attribute
137         i++;
138         WorkflowAttributeDefinition.Builder longXMLDef = WorkflowAttributeDefinition.Builder.create("XMLSearchableAttributeStdLong");
139         longXMLDef.addPropertyDefinition(TestXMLSearchableAttributeLong.SEARCH_STORAGE_KEY, TestXMLSearchableAttributeLong.SEARCH_STORAGE_VALUE.toString());
140         workflowDocument.addSearchableDefinition(longXMLDef.build());
141         // adding float searchable attribute
142         i++;
143         WorkflowAttributeDefinition.Builder floatXMLDef = WorkflowAttributeDefinition.Builder.create("XMLSearchableAttributeStdFloat");
144         floatXMLDef.addPropertyDefinition(TestXMLSearchableAttributeFloat.SEARCH_STORAGE_KEY, TestXMLSearchableAttributeFloat.SEARCH_STORAGE_VALUE.toString());
145         workflowDocument.addSearchableDefinition(floatXMLDef.build());
146         // adding string searchable attribute
147         i++;
148         WorkflowAttributeDefinition.Builder dateXMLDef = WorkflowAttributeDefinition.Builder.create("XMLSearchableAttributeStdDateTime");
149         dateXMLDef.addPropertyDefinition(TestXMLSearchableAttributeDateTime.SEARCH_STORAGE_KEY, DocumentSearchInternalUtils
150                 .getDisplayValueWithDateOnly(TestXMLSearchableAttributeDateTime.SEARCH_STORAGE_VALUE));
151         workflowDocument.addSearchableDefinition(dateXMLDef.build());
152 
153         workflowDocument.setTitle("Routing style");
154         workflowDocument.route("routing this document.");
155 
156         workflowDocument = WorkflowDocumentFactory.loadDocument(getPrincipalIdForName(userNetworkId), workflowDocument.getDocumentId());
157         DocumentRouteHeaderValue doc = KEWServiceLocator.getRouteHeaderService().getRouteHeader(workflowDocument.getDocumentId());
158 
159         DocumentSearchService docSearchService = (DocumentSearchService) KEWServiceLocator.getService(KEWServiceLocator.DOCUMENT_SEARCH_SERVICE);
160         Person user = KimApiServiceLocator.getPersonService().getPersonByPrincipalName(userNetworkId);
161 
162         DocumentSearchCriteria.Builder criteria = DocumentSearchCriteria.Builder.create();
163         criteria.setDocumentTypeName(documentTypeName);
164         addSearchableAttribute(criteria, TestXMLSearchableAttributeString.SEARCH_STORAGE_KEY,
165                 TestXMLSearchableAttributeString.SEARCH_STORAGE_VALUE);
166         DocumentSearchResults results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
167 
168         assertEquals("Search results should have one document.", 1, results.getSearchResults().size());
169 
170         DocumentSearchCriteria.Builder criteria2 = DocumentSearchCriteria.Builder.create();
171         criteria2.setDocumentTypeName(documentTypeName);
172         addSearchableAttribute(criteria2, TestXMLSearchableAttributeString.SEARCH_STORAGE_KEY, "fred");
173         DocumentSearchResults results2 = docSearchService.lookupDocuments(user.getPrincipalId(), criteria2.build());
174 
175         assertEquals("Search results should be empty.", 0, results2.getSearchResults().size());
176 
177         DocumentSearchCriteria.Builder criteria3 = DocumentSearchCriteria.Builder.create();
178         criteria3.setDocumentTypeName(documentTypeName);
179         addSearchableAttribute(criteria3, "fakeproperty", "doesntexist");
180         try {
181             docSearchService.lookupDocuments(user.getPrincipalId(), criteria3.build());
182             fail("Search results should be throwing a validation exception for use of non-existant searchable attribute");
183         } catch (RuntimeException e) {
184             assertTrue(e.getMessage().contains("LookupException"));
185         }
186 
187         criteria = null;
188         criteria = DocumentSearchCriteria.Builder.create();
189         criteria.setDocumentTypeName(documentTypeName);
190         addSearchableAttribute(criteria, TestXMLSearchableAttributeLong.SEARCH_STORAGE_KEY, TestXMLSearchableAttributeLong.SEARCH_STORAGE_VALUE.toString());
191         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
192         assertEquals("Search results should have one document.", 1, results.getSearchResults().size());
193 
194         criteria2 = null;
195         criteria2 = DocumentSearchCriteria.Builder.create();
196         criteria2.setDocumentTypeName(documentTypeName);
197         addSearchableAttribute(criteria2, TestXMLSearchableAttributeLong.SEARCH_STORAGE_KEY, "1111111");
198         results2 = docSearchService.lookupDocuments(user.getPrincipalId(), criteria2.build());
199         assertEquals("Search results should be empty.", 0, results2.getSearchResults().size());
200 
201         criteria3 = null;
202         criteria3 = DocumentSearchCriteria.Builder.create();
203         criteria3.setDocumentTypeName(documentTypeName);
204         addSearchableAttribute(criteria3, "fakeymcfakefake", "99999999");
205         try {
206             docSearchService.lookupDocuments(user.getPrincipalId(), criteria3.build());
207             fail("Search results should be throwing a validation exception for use of non-existant searchable attribute");
208         } catch (RuntimeException e) {
209             assertTrue(e.getMessage().contains("LookupException"));
210         }
211 
212         criteria = null;
213         criteria = DocumentSearchCriteria.Builder.create();
214         criteria.setDocumentTypeName(documentTypeName);
215         addSearchableAttribute(criteria, TestXMLSearchableAttributeFloat.SEARCH_STORAGE_KEY,
216                 TestXMLSearchableAttributeFloat.SEARCH_STORAGE_VALUE.toString());
217         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
218         assertEquals("Search results should have one document.", 1, results.getSearchResults().size());
219 
220         criteria2 = null;
221         criteria2 = DocumentSearchCriteria.Builder.create();
222         criteria2.setDocumentTypeName(documentTypeName);
223         addSearchableAttribute(criteria2, TestXMLSearchableAttributeFloat.SEARCH_STORAGE_KEY, "215.3548");
224         results2 = docSearchService.lookupDocuments(user.getPrincipalId(), criteria2.build());
225         assertEquals("Search results should be empty.", 0, results2.getSearchResults().size());
226 
227         criteria3 = null;
228         criteria3 = DocumentSearchCriteria.Builder.create();
229         criteria3.setDocumentTypeName(documentTypeName);
230         addSearchableAttribute(criteria3, "fakeylostington", "9999.9999");
231         try {
232             docSearchService.lookupDocuments(user.getPrincipalId(), criteria3.build());
233             fail("Search results should be throwing a validation exception for use of non-existant searchable attribute");
234         } catch (RuntimeException wsee) {
235             assertTrue(wsee.getMessage().contains("LookupException"));
236         }
237 
238         criteria = null;
239         criteria = DocumentSearchCriteria.Builder.create();
240         criteria.setDocumentTypeName(documentTypeName);
241         addSearchableAttribute(criteria, TestXMLSearchableAttributeDateTime.SEARCH_STORAGE_KEY,
242                 DocumentSearchInternalUtils.getDisplayValueWithDateOnly(new Timestamp(
243                         TestXMLSearchableAttributeDateTime.SEARCH_STORAGE_VALUE_IN_MILLS)));
244         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
245         assertEquals("Search results should have one document.", 1, results.getSearchResults().size());
246 
247         criteria2 = null;
248         criteria2 = DocumentSearchCriteria.Builder.create();
249         criteria2.setDocumentTypeName(documentTypeName);
250         addSearchableAttribute(criteria2, TestXMLSearchableAttributeDateTime.SEARCH_STORAGE_KEY, "07/06/1979");
251         results2 = docSearchService.lookupDocuments(user.getPrincipalId(), criteria2.build());
252         assertEquals("Search results should be empty.", 0, results2.getSearchResults().size());
253 
254         criteria3 = null;
255         criteria3 = DocumentSearchCriteria.Builder.create();
256         criteria3.setDocumentTypeName(documentTypeName);
257         addSearchableAttribute(criteria3, "lastingsfakerson", "07/06/2007");
258         try {
259             docSearchService.lookupDocuments(user.getPrincipalId(), criteria3.build());
260             fail("Search results should be throwing a validation exception for use of non-existant searchable attribute");
261         } catch (RuntimeException wsee) {
262             assertTrue(wsee.getMessage().contains("LookupException"));
263         }
264     }
265 
266     @Test public void testRouteDocumentWithSearchableAttribute() throws Exception {
267         String documentTypeName = "SearchDocType";
268         String key = "givenname";
269         DocumentType docType = ((DocumentTypeService)KEWServiceLocator.getService(KEWServiceLocator.DOCUMENT_TYPE_SERVICE)).findByName(documentTypeName);
270         WorkflowDocument workflowDocument = WorkflowDocumentFactory.createDocument(getPrincipalIdForName("rkirkend"), documentTypeName);
271         WorkflowAttributeDefinition.Builder givennameXMLDef = WorkflowAttributeDefinition.Builder.create("XMLSearchableAttribute");
272 
273         workflowDocument.setApplicationContent("<test></test>");
274 
275         givennameXMLDef.addPropertyDefinition(key, "jack");
276         workflowDocument.addSearchableDefinition(givennameXMLDef.build());
277 
278         workflowDocument.setTitle("Routing style");
279         workflowDocument.route("routing this document.");
280 
281         DocumentSearchService docSearchService = (DocumentSearchService) KEWServiceLocator.getService(KEWServiceLocator.DOCUMENT_SEARCH_SERVICE);
282 
283         Person user = KimApiServiceLocator.getPersonService().getPersonByPrincipalName("rkirkend");
284         DocumentSearchCriteria.Builder criteria = DocumentSearchCriteria.Builder.create();
285         criteria.setDocumentTypeName(documentTypeName);
286         addSearchableAttribute(criteria, key, "jack");
287         DocumentSearchResults results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
288 
289         assertEquals("Search results should have one document.", 1, results.getSearchResults().size());
290 
291         criteria = null;
292         criteria = DocumentSearchCriteria.Builder.create();
293         criteria.setDocumentTypeName(documentTypeName);
294         addSearchableAttribute(criteria, key, "fred");
295         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
296 
297         assertEquals("Search results should be empty.", 0, results.getSearchResults().size());
298 
299         criteria = null;
300         criteria = DocumentSearchCriteria.Builder.create();
301         criteria.setDocumentTypeName(documentTypeName);
302         addSearchableAttribute(criteria, "fakeproperty", "doesntexist");
303         try {
304             docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
305             fail("Search results should be throwing a validation exception for use of non-existant searchable attribute");
306         } catch (RuntimeException wsee) {
307             assertTrue(wsee.getMessage().contains("LookupException"));
308         }
309     }
310 
311     @Test public void testDocumentSearchAttributeWildcarding() throws Exception {
312         DocumentSearchService docSearchService = (DocumentSearchService) KEWServiceLocator.getService(KEWServiceLocator.DOCUMENT_SEARCH_SERVICE);
313 
314         String documentTypeName = "SearchDocType";
315         String key = "givenname";
316         DocumentType docType = ((DocumentTypeService)KEWServiceLocator.getService(KEWServiceLocator.DOCUMENT_TYPE_SERVICE)).findByName(
317                 documentTypeName);
318         WorkflowDocument workflowDocument = WorkflowDocumentFactory.createDocument(getPrincipalIdForName("rkirkend"),
319                 documentTypeName);
320         WorkflowAttributeDefinition.Builder givennameXMLDef = WorkflowAttributeDefinition.Builder.create("XMLSearchableAttribute");
321 
322         workflowDocument.setApplicationContent("<test></test>");
323 
324         givennameXMLDef.addPropertyDefinition(key, "jack");
325         workflowDocument.addSearchableDefinition(givennameXMLDef.build());
326 
327         workflowDocument.setTitle("Routing style");
328         workflowDocument.route("routing this document.");
329 
330         Person user = KimApiServiceLocator.getPersonService().getPersonByPrincipalName("rkirkend");
331         DocumentSearchCriteria.Builder criteria = DocumentSearchCriteria.Builder.create();
332         criteria.setDocumentTypeName(documentTypeName);
333         addSearchableAttribute(criteria, key, "jack");
334         DocumentSearchResults results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
335 
336         assertEquals("Search results should have one document.", 1, results.getSearchResults().size());
337 
338         criteria = DocumentSearchCriteria.Builder.create();
339         criteria.setDocumentTypeName(documentTypeName);
340         addSearchableAttribute(criteria, key, "ja*");
341         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
342 
343         assertEquals("Search results should have one document.", 1, results.getSearchResults().size());
344 
345         criteria = DocumentSearchCriteria.Builder.create();
346         criteria.setDocumentTypeName(documentTypeName);
347         addSearchableAttribute(criteria, key, "ja");
348         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
349 
350         assertEquals("Search results should have one document.", 0, results.getSearchResults().size());
351 
352         criteria = DocumentSearchCriteria.Builder.create();
353         criteria.setDocumentTypeName(documentTypeName);
354         addSearchableAttribute(criteria, key, "*ack");
355         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
356 
357         assertEquals("Search results should have one document.", 1, results.getSearchResults().size());
358     }
359 
360     @Test public void testDocumentSearchAttributeWildcardingDisallow() throws Exception {
361         DocumentSearchService docSearchService = (DocumentSearchService) KEWServiceLocator.getService(KEWServiceLocator.DOCUMENT_SEARCH_SERVICE);
362 
363         String documentTypeName = "SearchDocTypeStandardSearchDataType";
364         DocumentType docType = ((DocumentTypeService)KEWServiceLocator.getService(KEWServiceLocator.DOCUMENT_TYPE_SERVICE)).findByName(documentTypeName);
365         String userNetworkId = "rkirkend";
366         WorkflowDocument workflowDocument = WorkflowDocumentFactory.createDocument(getPrincipalIdForName(userNetworkId), documentTypeName);
367 
368         /*
369          *   Below we are using the keys and values from the custom searchable attribute classes' static constants but
370          *   this is only for convenience as those should always be valid values to test for.
371          */
372         WorkflowAttributeDefinition.Builder longXMLDef = WorkflowAttributeDefinition.Builder.create("XMLSearchableAttributeStdLong");
373         longXMLDef.addPropertyDefinition(TestXMLSearchableAttributeLong.SEARCH_STORAGE_KEY, TestXMLSearchableAttributeLong.SEARCH_STORAGE_VALUE.toString());
374         workflowDocument.addSearchableDefinition(longXMLDef.build());
375         workflowDocument.setTitle("Routing style");
376         workflowDocument.route("routing this document.");
377 
378         Person user = KimApiServiceLocator.getPersonService().getPersonByPrincipalName(userNetworkId);
379 
380         String validSearchValue = TestXMLSearchableAttributeLong.SEARCH_STORAGE_VALUE.toString();
381         DocumentSearchCriteria.Builder criteria = null;
382         DocumentSearchResults results = null;
383         criteria = DocumentSearchCriteria.Builder.create();
384         criteria.setDocumentTypeName(documentTypeName);
385         addSearchableAttribute(criteria, TestXMLSearchableAttributeLong.SEARCH_STORAGE_KEY, validSearchValue);
386         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
387         assertEquals("Search results should have one document.", 1, results.getSearchResults().size());
388 
389         criteria = DocumentSearchCriteria.Builder.create();
390         criteria.setDocumentTypeName(documentTypeName);
391         addSearchableAttribute(criteria, TestXMLSearchableAttributeLong.SEARCH_STORAGE_KEY,
392                 "*" + validSearchValue.substring(2));
393 
394         if ((new SearchableAttributeLongValue()).allowsWildcards()) {
395             results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
396             assertEquals("Search results should be empty using wildcard '*' value.", 0, results.getSearchResults().size());
397         } else {
398             try {
399                 docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
400                 fail("Search results should be throwing a validation exception for use of the character '*' without allowing wildcards");
401             } catch (WorkflowServiceErrorException wsee) {}
402         }
403 
404         criteria = DocumentSearchCriteria.Builder.create();
405         criteria.setDocumentTypeName(documentTypeName);
406         addSearchableAttribute(criteria, TestXMLSearchableAttributeLong.SEARCH_STORAGE_KEY, validSearchValue.substring(
407                 0, (validSearchValue.length() - 2)));
408         results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
409         assertEquals("Search results should be empty trying to use assumed ending wildcard.", 0, results.getSearchResults().size());
410     }
411 
412     @Test public void testDocumentSearchAttributeCaseSensitivity() throws Exception {
413         DocumentSearchService docSearchService = (DocumentSearchService) KEWServiceLocator.getService(KEWServiceLocator.DOCUMENT_SEARCH_SERVICE);
414         String documentTypeName = "SearchDocTypeCaseSensitivity";
415         String networkId = "rkirkend";
416         DocumentType docType = ((DocumentTypeService)KEWServiceLocator.getService(KEWServiceLocator.DOCUMENT_TYPE_SERVICE)).findByName(documentTypeName);
417 
418         String key = "givenname";
419         WorkflowDocument workflowDocument = WorkflowDocumentFactory.createDocument(getPrincipalIdForName(networkId), documentTypeName);
420         WorkflowAttributeDefinition.Builder givennameXMLDef = WorkflowAttributeDefinition.Builder.create("XMLSearchableAttribute");
421         givennameXMLDef.addPropertyDefinition(key, "jack");
422         workflowDocument.addSearchableDefinition(givennameXMLDef.build());
423         workflowDocument.setTitle("Routing style");
424         workflowDocument.route("routing this document.");
425 
426         Person user = KimApiServiceLocator.getPersonService().getPersonByPrincipalName(networkId);
427         DocumentSearchCriteria.Builder criteria = DocumentSearchCriteria.Builder.create();
428         criteria.setDocumentTypeName(documentTypeName);
429         addSearchableAttribute(criteria, key, "jack");
430         DocumentSearchResults results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
431         assertEquals("Search results should have one document.", 1, results.getSearchResults().size());
432 
433         criteria = DocumentSearchCriteria.Builder.create();
434         criteria.setDocumentTypeName(documentTypeName);
435         addSearchableAttribute(criteria, key, "JACK");
436         results =  docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
437         assertEquals("Search results should have one document.", 1, results.getSearchResults().size());
438 
439         criteria = DocumentSearchCriteria.Builder.create();
440         criteria.setDocumentTypeName(documentTypeName);
441         addSearchableAttribute(criteria, key, "jAck");
442         results =  docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
443         assertEquals("Search results should have one document.", 1, results.getSearchResults().size());
444 
445         criteria = DocumentSearchCriteria.Builder.create();
446         criteria.setDocumentTypeName(documentTypeName);
447         addSearchableAttribute(criteria, key, "jacK");
448         results =  docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
449         assertEquals("Search results should have one document.", 1, results.getSearchResults().size());
450 
451         key = "givenname_nocase";
452         workflowDocument = WorkflowDocumentFactory.createDocument(getPrincipalIdForName(networkId), documentTypeName);
453         WorkflowAttributeDefinition.Builder givenname_nocaseXMLDef = WorkflowAttributeDefinition.Builder.create("XMLSearchableAttribute_CaseInsensitive");
454         givenname_nocaseXMLDef.addPropertyDefinition(key, "jaCk");
455         workflowDocument.addSearchableDefinition(givenname_nocaseXMLDef.build());
456         workflowDocument.setTitle("Routing style");
457         workflowDocument.route("routing this document.");
458 
459         criteria = DocumentSearchCriteria.Builder.create();
460         criteria.setDocumentTypeName(documentTypeName);
461         addSearchableAttribute(criteria, key, "jack");
462         results =  docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
463         assertEquals("Search results should have one document.", 1, results.getSearchResults().size());
464 
465         criteria = DocumentSearchCriteria.Builder.create();
466         criteria.setDocumentTypeName(documentTypeName);
467         addSearchableAttribute(criteria, key, "JACK");
468         results =  docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
469         assertEquals("Search results should have one document.", 1, results.getSearchResults().size());
470 
471         criteria = DocumentSearchCriteria.Builder.create();
472         criteria.setDocumentTypeName(documentTypeName);
473         addSearchableAttribute(criteria, key, "jaCk");
474         results =  docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
475         assertEquals("Search results should have one document.", 1, results.getSearchResults().size());
476 
477         criteria = DocumentSearchCriteria.Builder.create();
478         criteria.setDocumentTypeName(documentTypeName);
479         addSearchableAttribute(criteria, key, "jacK");
480         results =  docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
481         assertEquals("Search results should have one document.", 1, results.getSearchResults().size());
482 
483         criteria = DocumentSearchCriteria.Builder.create();
484         criteria.setDocumentTypeName(documentTypeName);
485         addSearchableAttribute(criteria, key, "jAc");
486         results =  docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
487         assertEquals("Search results should have one document.", 0, results.getSearchResults().size());
488 
489         criteria = DocumentSearchCriteria.Builder.create();
490         criteria.setDocumentTypeName(documentTypeName);
491         addSearchableAttribute(criteria, key, "jA*");
492         results =  docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
493         assertEquals("Search results should have one document.", 1, results.getSearchResults().size());
494 
495         criteria = DocumentSearchCriteria.Builder.create();
496         criteria.setDocumentTypeName(documentTypeName);
497         addSearchableAttribute(criteria, key, "*aCk");
498         results =  docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
499         assertEquals("Search results should have one document.", 1, results.getSearchResults().size());
500     }
501 
502     /**
503      * Tests searching with client-generated documentContent which is just malformed XML.
504      * @throws WorkflowException
505      */
506     @Test public void testRouteDocumentWithMalformedSearchableAttributeContent() throws WorkflowException {
507         WorkflowDocument workflowDocument = WorkflowDocumentFactory.createDocument(getPrincipalIdForName("rkirkend"), "SearchDocType");
508 
509         workflowDocument.setApplicationContent("hey, <I'm Not ] Even & XML");
510 
511         workflowDocument.setTitle("Routing style");
512         try {
513             workflowDocument.route("routing this document.");
514             fail("routeDocument succeeded with malformed XML");
515         } catch (Exception we) {
516             // An exception is thrown in DTOConverter/XmlUtils.appendXml at the time of this writing
517             // so I will just assume that is the expected behavior
518         }
519         TestUtilities.waitForExceptionRouting();
520     }
521 
522     /**
523      * Tests searching with client-generated documentContent which will not match what the SearchableAttributeOld
524      * is configured to look for.  This should pass with zero search results, but should not throw an exception.
525      * @throws Exception
526      */
527     @Test public void testRouteDocumentWithInvalidSearchableAttributeContent() throws Exception {
528         String documentTypeName = "SearchDocType";
529         String key = "givenname";
530         DocumentType docType = ((DocumentTypeService)KEWServiceLocator.getService(KEWServiceLocator.DOCUMENT_TYPE_SERVICE)).findByName(documentTypeName);
531         WorkflowDocument workflowDocument = WorkflowDocumentFactory.createDocument(getPrincipalIdForName("rkirkend"), documentTypeName);
532 
533         workflowDocument.setApplicationContent("<documentContent><searchableContent><garbage>" +
534                                                "<blah>not going to match anything</blah>" +
535                                                "</garbage></searchableContent></documentContent>");
536 
537         workflowDocument.setTitle("Routing style");
538         workflowDocument.route("routing this document.");
539 
540         DocumentSearchService docSearchService = (DocumentSearchService) KEWServiceLocator.getService(KEWServiceLocator.DOCUMENT_SEARCH_SERVICE);
541 
542         Person user = KimApiServiceLocator.getPersonService().getPersonByPrincipalName("rkirkend");
543         DocumentSearchCriteria.Builder criteria = DocumentSearchCriteria.Builder.create();
544         criteria.setDocumentTypeName(documentTypeName);
545         addSearchableAttribute(criteria, key, "jack");
546         DocumentSearchResults results =  docSearchService.lookupDocuments(user.getPrincipalId(),
547                 criteria.build());
548 
549         assertEquals("Search results should be empty.", 0, results.getSearchResults().size());
550 
551         criteria = DocumentSearchCriteria.Builder.create();
552         criteria.setDocumentTypeName(documentTypeName);
553         addSearchableAttribute(criteria, key, "fred");
554         results =  docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
555 
556         assertEquals("Search results should be empty.", 0, results.getSearchResults().size());
557 
558         criteria = DocumentSearchCriteria.Builder.create();
559         criteria.setDocumentTypeName(documentTypeName);
560         addSearchableAttribute(criteria, "fakeproperty", "doesntexist");
561         try {
562             results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
563             fail("Search results should be throwing a validation exception for use of non-existant searchable attribute");
564         } catch (RuntimeException wsee) {
565             assertTrue(wsee.getMessage().contains("LookupException"));
566         }
567     }
568 
569     /**
570      * Tests searching with client-generated documentContent which will not match what the SearchableAttributeOld
571      * is configured to look for.  This should pass with zero search results, but should not throw an exception.
572      * @throws Exception
573      */
574     @Test public void testRouteDocumentWithMoreInvalidSearchableAttributeContent() throws Exception {
575         String documentTypeName = "SearchDocType";
576         String key = "givenname";
577         DocumentType docType = ((DocumentTypeService)KEWServiceLocator.getService(KEWServiceLocator.DOCUMENT_TYPE_SERVICE)).findByName(documentTypeName);
578         WorkflowDocument workflowDocument = WorkflowDocumentFactory.createDocument(getPrincipalIdForName("rkirkend"), documentTypeName);
579 
580         workflowDocument.setApplicationContent("<documentContent><NOTsearchableContent><garbage>" +
581                                                "<blah>not going to match anything</blah>" +
582                                                "</garbage></NOTsearchableContent></documentContent>");
583 
584         workflowDocument.setTitle("Routing style");
585         workflowDocument.route("routing this document.");
586 
587         DocumentSearchService docSearchService = (DocumentSearchService) KEWServiceLocator.getService(KEWServiceLocator.DOCUMENT_SEARCH_SERVICE);
588 
589         Person user = KimApiServiceLocator.getPersonService().getPersonByPrincipalName("rkirkend");
590         DocumentSearchCriteria.Builder criteria = DocumentSearchCriteria.Builder.create();
591         criteria.setDocumentTypeName(documentTypeName);
592         addSearchableAttribute(criteria, key, "jack");
593         DocumentSearchResults results =  docSearchService.lookupDocuments(user.getPrincipalId(),
594                 criteria.build());
595 
596         assertEquals("Search results should be empty.", 0, results.getSearchResults().size());
597 
598         criteria = DocumentSearchCriteria.Builder.create();
599         criteria.setDocumentTypeName(documentTypeName);
600         addSearchableAttribute(criteria, key, "fred");
601         results =  docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
602 
603         assertEquals("Search results should be empty.", 0, results.getSearchResults().size());
604 
605         criteria = DocumentSearchCriteria.Builder.create();
606         criteria.setDocumentTypeName(documentTypeName);
607         addSearchableAttribute(criteria, "fakeproperty", "doesntexist");
608         try {
609             results =  docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
610             fail("Search results should be throwing a validation exception for use of non-existant searchable attribute");
611         } catch (RuntimeException wsee) {
612             assertTrue(wsee.getMessage().contains("LookupException"));
613         }
614     }
615 
616     /*
617      * Test method for 'org.kuali.rice.kew.docsearch.xml.StandardGenericXMLSearchableAttribute.getSearchContent()'
618      */
619     @Test public void testGetSearchContent() throws Exception {
620         //Filling in a random document type name... Revisit
621         String documentTypeName = "SearchDocType";
622 
623         assertGeneratedSearchContent(documentTypeName, "XMLSearchableAttribute", "givenname", "value", "//putWhateverWordsIwantInsideThisTag/givenname/value");
624         assertGeneratedSearchContent(documentTypeName, "XMLSearchableAttributeStdLong", "testLongKey", "123458", "//putWhateverWordsIwantInsideThisTag/testLongKey/value");
625         assertGeneratedSearchContent(documentTypeName, "XMLSearchableAttributeStdFloat", "testFloatKey", "2568.204", "//putWhateverWordsIwantInsideThisTag/testFloatKey/value");
626         assertGeneratedSearchContent(documentTypeName, "XMLSearchableAttributeStdCurrency", "testCurrencyKey", "2248.20", "//putWhateverWordsIwantInsideThisTag/testCurrencyKey/value");
627         String value = DocumentSearchInternalUtils.getDisplayValueWithDateOnly(TestXMLSearchableAttributeDateTime.SEARCH_STORAGE_VALUE);
628         assertGeneratedSearchContent(documentTypeName, "XMLSearchableAttributeStdDateTime", "testDateTimeKey", value, "//putWhateverWordsIwantInsideThisTag/testDateTimeKey/value");
629     }
630 
631     /**
632      * Helper for asserting generated search content
633      */
634     protected void assertGeneratedSearchContent(String docType, String attrName, String key, String value, String expr) throws Exception {
635         StandardGenericXMLSearchableAttribute attribute = getAttribute(attrName);
636         ExtensionDefinition ed = createExtensionDefinition(attrName);
637         WorkflowAttributeDefinition.Builder wad = WorkflowAttributeDefinition.Builder.create(attrName);
638         wad.addPropertyDefinition(key, value);
639         String searchContent = attribute.generateSearchContent(ed, docType, wad.build());
640         assertTrue("searchContent was not found.", searchContent != null && searchContent.length() > 0);
641         XPath xpath = XPathFactory.newInstance().newXPath();
642         Element foundDocContent = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new InputSource(new BufferedReader(new StringReader(searchContent)))).getDocumentElement();
643         assertTrue("Search content does not contain correct value for field '" + key + "'.", value.equals(xpath.evaluate(expr, foundDocContent, XPathConstants.STRING)));
644     }
645 
646     /*
647      * Test method for 'org.kuali.rice.kew.docsearch.xml.StandardGenericXMLSearchableAttribute.getSearchStorageValues(String)'
648      */
649     @Test public void testExtractDocumentAttributes() throws ParseException {
650         //Filling in a random document type name... Revisit
651         String documentTypeName = "SearchDocType";
652 
653         String key = "givenname";
654         String value = "jack";
655         assertExtractDocumentAttributes(documentTypeName, "XMLSearchableAttribute", key, value, "<putWhateverWordsIwantInsideThisTag>" + "<" + key + ">" + "<value>" + value + "</value>" + "</" + key+ ">" + "</putWhateverWordsIwantInsideThisTag>");
656 
657         // test general operation
658         key = "testLongKey";
659         value = "123458";
660         assertExtractDocumentAttributes(documentTypeName, "XMLSearchableAttributeStdLong", key, new BigInteger(value), "<putWhateverWordsIwantInsideThisTag>" + "<" + key + ">" + "<value>" + value + "</value>" + "</" + key+ ">" + "</putWhateverWordsIwantInsideThisTag>");
661         // test operation with leading and trailing spaces in xml doc content
662         assertExtractDocumentAttributes(documentTypeName, "XMLSearchableAttributeStdLong", key, new BigInteger(value), "<putWhateverWordsIwantInsideThisTag>" + "<" + key + ">" + "<value>" + " " + value + " " + "</value>" + "</" + key + ">" + "</putWhateverWordsIwantInsideThisTag>");
663 
664         key = "testFloatKey";
665         value = "2568.204154796";
666         assertExtractDocumentAttributes(documentTypeName, "XMLSearchableAttributeStdFloat", key, new BigDecimal(value), "<putWhateverWordsIwantInsideThisTag>" + "<" + key+ ">" + "<value>" + value + "</value>" + "</" + key + ">" + "</putWhateverWordsIwantInsideThisTag>");
667 
668         key = "testDateTimeKey";
669         value = DocumentSearchInternalUtils.getDisplayValueWithDateOnly(TestXMLSearchableAttributeDateTime.SEARCH_STORAGE_VALUE);
670         // value is coerced to Date without time
671         DateMidnight expected = TestXMLSearchableAttributeDateTime.SEARCH_STORAGE_VALUE.toDateMidnight();
672         assertExtractDocumentAttributes(documentTypeName, "XMLSearchableAttributeStdDateTime", key, expected, "<putWhateverWordsIwantInsideThisTag>" + "<" + key + ">" + "<value>" + value + "</value>" + "</" + key + ">" + "</putWhateverWordsIwantInsideThisTag>");
673 
674         // test for kuali xstream formatted dates
675         value = "02/20/2007";
676         String returnValue = "02/20/2007";
677         // value is coerced to Date without time
678         Date d = RiceConstants.getDefaultDateFormat().parse(returnValue);
679         expected = new DateTime(d.getTime()).toDateMidnight();
680         assertExtractDocumentAttributes(documentTypeName, "XMLSearchableAttributeStdDateTime", key, expected, "<putWhateverWordsIwantInsideThisTag>" + "<" + key + ">" + "<value>" + value + "</value>" + "</" + key + ">" + "</putWhateverWordsIwantInsideThisTag>");
681     }
682 
683     /**
684      * Helper to create documentwithcontent with searchable content
685      */
686     protected DocumentWithContent createDocumentWithSearchableContent(String docType, String content) {
687         Document doc = Document.Builder.create("fakeDocId123", "fake initiator", docType, "fake doc type id").build();
688         DocumentContent.Builder c = DocumentContent.Builder.create("fakeDocId123");
689         c.setSearchableContent(content);
690         return DocumentWithContent.create(doc, c.build());
691     }
692 
693     /**
694      * Help for asserting extracted document attributes
695      */
696     protected void assertExtractDocumentAttributes(String docType, String attrName, String key, Object value, String searchableContent) {
697         StandardGenericXMLSearchableAttribute attribute = getAttribute(attrName);
698         ExtensionDefinition ed = createExtensionDefinition(attrName);
699         List<DocumentAttribute> values = attribute.extractDocumentAttributes(ed, createDocumentWithSearchableContent(docType, searchableContent));
700         assertEquals("Number of search attribute values is wrong",1,values.size());
701         for (DocumentAttribute attrib: values) {
702             assertEquals("Key of attribute is wrong",key, attrib.getName());
703             assertEquals("Value of attribute is wrong",value, attrib.getValue());
704         }
705     }
706     
707     private String insertCommasIfNeeded(String value, int interval) {
708         int indexOfDecimal = value.indexOf(".");
709         String decimalPointOn = value.substring(indexOfDecimal);
710         String temp = value.substring(0, indexOfDecimal);
711         StringBuffer builtValue = new StringBuffer();
712         if (temp.length() <= interval) {
713             builtValue.append(temp);
714         } else {
715             int counter = 0;
716             for (int i = temp.length() - 1; (i >= 0); i--) {
717                 if (counter == interval) {
718                     builtValue.insert(0, ",");
719                     counter = 0;
720                 }
721                 counter++;
722                 builtValue.insert(0, temp.substring(i, i+1));
723             }
724         }
725         return (builtValue.append(decimalPointOn)).toString();
726     }
727 
728     /*
729      * Test method for 'org.kuali.rice.kew.docsearch.xml.StandardGenericXMLSearchableAttribute.getSearchingRows()'
730      */
731     @Test public void testGetSearchFields() {
732         //Filling in a random document type name... Revisit
733         String documentTypeName = "SearchDocType";
734         StandardGenericXMLSearchableAttribute searchAttribute = getAttribute(null);
735 
736         ExtensionDefinition ed = createExtensionDefinition("XMLSearchableAttribute");
737         List<RemotableAttributeField> remotableAttributeFields = searchAttribute.getSearchFields(ed, documentTypeName);
738         List<Row> rows = FieldUtils.convertRemotableAttributeFields(remotableAttributeFields);
739         assertTrue("Invalid number of search rows", rows.size() == 1);
740 
741         //we really just want this to load without exploding
742         searchAttribute = getAttribute("BlankDropDownSearchAttribute");
743         ed = createExtensionDefinition("BlankDropDownSearchAttribute");
744         remotableAttributeFields = searchAttribute.getSearchFields(ed, documentTypeName);
745         rows = FieldUtils.convertRemotableAttributeFields(remotableAttributeFields);
746         assertEquals("Invalid number of search rows", 1, rows.size());
747         Row row = (Row) rows.get(0);
748         Field field = row.getField(0);
749         assertEquals("Should be 5 valid values", 5, field.getFieldValidValues().size());
750 
751         assertEquals("Default value is not correct", "AMST", field.getPropertyValue());
752     }
753 
754     /*
755      * Test method for 'org.kuali.rice.kew.docsearch.xml.StandardGenericXMLSearchableAttribute.validateUserSearchInputs(Map)'
756      */
757     @Test  public void testValidateUserSearchInputs() {
758         String documentTypeName = "SearchDocType";
759 
760         StandardGenericXMLSearchableAttribute searchAttribute = getAttribute("XMLSearchableAttribute");
761         ExtensionDefinition ed = createExtensionDefinition("XMLSearchableAttribute");
762         assertDocumentSearchCriteriaValidation(searchAttribute, ed, TestXMLSearchableAttributeString.SEARCH_STORAGE_KEY, "jack", false);
763         assertDocumentSearchCriteriaValidation(searchAttribute, ed, TestXMLSearchableAttributeString.SEARCH_STORAGE_KEY, "jack.jack", true);
764         // NOTE: the original intent of this particular test is unclear. it currently passes since the wildcard character is stripped
765         assertDocumentSearchCriteriaValidation(searchAttribute, ed, TestXMLSearchableAttributeString.SEARCH_STORAGE_KEY,  "jack*jack", false);
766 
767         searchAttribute = getAttribute("XMLSearchableAttributeStdLong");
768         ed = createExtensionDefinition("XMLSearchableAttributeStdLong");
769         assertDocumentSearchCriteriaValidation(searchAttribute, ed, TestXMLSearchableAttributeLong.SEARCH_STORAGE_KEY, TestXMLSearchableAttributeLong.SEARCH_STORAGE_VALUE.toString(), false);
770         RemotableAttributeError error = assertDocumentSearchCriteriaValidation(searchAttribute, ed, TestXMLSearchableAttributeLong.SEARCH_STORAGE_KEY, TestXMLSearchableAttributeLong.SEARCH_STORAGE_VALUE.toString() + ".33", true);
771         assertTrue("Validation error is incorrect", error.getMessage().endsWith("does not conform to standard validation for field type."));
772         error = assertDocumentSearchCriteriaValidation(searchAttribute, ed, TestXMLSearchableAttributeLong.SEARCH_STORAGE_KEY, TestXMLSearchableAttributeLong.SEARCH_STORAGE_VALUE.toString() + "jack*jack", true);
773         assertTrue("Validation error is incorrect", error.getMessage().endsWith("does not conform to standard validation for field type."));
774 
775         searchAttribute = getAttribute("XMLSearchableAttributeStdFloat");
776         ed = createExtensionDefinition("XMLSearchableAttributeStdFloat");
777         assertDocumentSearchCriteriaValidation(searchAttribute, ed, TestXMLSearchableAttributeFloat.SEARCH_STORAGE_KEY, TestXMLSearchableAttributeFloat.SEARCH_STORAGE_VALUE.toString(), false);
778         error = assertDocumentSearchCriteriaValidation(searchAttribute, ed, TestXMLSearchableAttributeFloat.SEARCH_STORAGE_KEY, TestXMLSearchableAttributeFloat.SEARCH_STORAGE_VALUE.toString() + "a", true);
779         assertTrue("Validation error is incorrect", error.getMessage().endsWith("does not conform to standard validation for field type."));
780         error = assertDocumentSearchCriteriaValidation(searchAttribute, ed, TestXMLSearchableAttributeFloat.SEARCH_STORAGE_KEY, TestXMLSearchableAttributeFloat.SEARCH_STORAGE_VALUE.toString() + "*", true);
781         assertTrue("Validation error is incorrect", error.getMessage().endsWith("does not conform to standard validation for field type."));
782 
783         searchAttribute = getAttribute("XMLSearchableAttributeStdCurrency");
784         ed = createExtensionDefinition("XMLSearchableAttributeStdCurrency");
785         String key = "testCurrencyKey";
786         Float value = Float.valueOf("5486.25");
787         assertDocumentSearchCriteriaValidation(searchAttribute, ed, key, value.toString(), false);
788         error = assertDocumentSearchCriteriaValidation(searchAttribute, ed, key, value.toString() + "a", true);
789         assertTrue("Validation error is incorrect", error.getMessage().endsWith("does not conform to standard validation for field type."));
790         error = assertDocumentSearchCriteriaValidation(searchAttribute, ed, key, value.toString() + "*", true);
791         assertTrue("Validation error is incorrect", error.getMessage().endsWith("does not conform to standard validation for field type."));
792 
793         searchAttribute = getAttribute("XMLSearchableAttributeStdDateTime");
794         ed = createExtensionDefinition("XMLSearchableAttributeStdDateTime");
795         assertDocumentSearchCriteriaValidation(searchAttribute, ed, TestXMLSearchableAttributeDateTime.SEARCH_STORAGE_KEY, DocumentSearchInternalUtils.getDisplayValueWithDateOnly(TestXMLSearchableAttributeDateTime.SEARCH_STORAGE_VALUE), false);
796         assertDocumentSearchCriteriaValidation(searchAttribute, ed, TestXMLSearchableAttributeDateTime.SEARCH_STORAGE_KEY, "001/5/08", false);
797         error = assertDocumentSearchCriteriaValidation(searchAttribute, ed, TestXMLSearchableAttributeDateTime.SEARCH_STORAGE_KEY, "41/5/08", true);
798         assertTrue("Validation error is incorrect", error.getMessage().endsWith("does not conform to standard validation for field type."));
799         error = assertDocumentSearchCriteriaValidation(searchAttribute, ed, TestXMLSearchableAttributeDateTime.SEARCH_STORAGE_KEY, "01/02/20*", true);
800         assertTrue("Validation error is incorrect", error.getMessage().endsWith("does not conform to standard validation for field type."));
801     }
802 
803     /**
804      * Helper to assert document search criteria validation
805      */
806     protected RemotableAttributeError assertDocumentSearchCriteriaValidation(StandardGenericXMLSearchableAttribute attribute, ExtensionDefinition ed, String attrkey, String attrvalue, boolean expectError) {
807         DocumentSearchCriteria.Builder dscb = DocumentSearchCriteria.Builder.create();
808         dscb.addDocumentAttributeValue(attrkey, attrvalue);
809 
810         List<RemotableAttributeError> errors = attribute.validateDocumentAttributeCriteria(ed, dscb.build());
811 
812         if (expectError) {
813             assertEquals("Validation should return a single error message.", 1, errors.size());
814             return errors.get(0);
815         } else {
816             assertEquals("Validation should not have returned an error.", 0, errors.size());
817             return null;
818         }
819     }
820 
821     /**
822      * Tests the XStreamSafeEvaluator against searchable attributes to resolve EN-63 and KULWF-723.
823      *
824      * This test is pretty much just a copy of testRouteDocumentWithSearchableAttribute using a
825      * different document type which defines the same xpath expression, only with embedded
826      * XStream "reference" attributes in the XML.
827      */
828     @Test public void testRouteDocumentWithXStreamSearchableAttribute() throws Exception {
829         String documentTypeName = "SearchDocType";
830         String key = "givenname";
831         DocumentType docType = ((DocumentTypeService)KEWServiceLocator.getService(KEWServiceLocator.DOCUMENT_TYPE_SERVICE)).findByName(documentTypeName);
832 
833         WorkflowDocument workflowDocument = WorkflowDocumentFactory.createDocument(getPrincipalIdForName("rkirkend"), "SearchDocTypeXStream");
834         WorkflowAttributeDefinition.Builder givennameXMLDef = WorkflowAttributeDefinition.Builder.create("XMLXStreamSearchableAttribute");
835 
836         workflowDocument.setApplicationContent("<test></test>");
837 
838         givennameXMLDef.addPropertyDefinition("givenname", "jack");
839         workflowDocument.addSearchableDefinition(givennameXMLDef.build());
840 
841         workflowDocument.setTitle("Routing style");
842         workflowDocument.route("routing this document.");
843 
844         DocumentSearchService docSearchService = (DocumentSearchService) KEWServiceLocator.getService(KEWServiceLocator.DOCUMENT_SEARCH_SERVICE);
845 
846         Person user = KimApiServiceLocator.getPersonService().getPersonByPrincipalName("rkirkend");
847         DocumentSearchCriteria.Builder criteria = DocumentSearchCriteria.Builder.create();
848         criteria.setDocumentTypeName(documentTypeName);
849         addSearchableAttribute(criteria, key, "jack");
850         DocumentSearchResults results =  docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
851 
852         assertEquals("Search results should be empty.", 0, results.getSearchResults().size());
853 
854         criteria = DocumentSearchCriteria.Builder.create();
855         criteria.setDocumentTypeName(documentTypeName);
856         addSearchableAttribute(criteria, key, "fred");
857         results =  docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
858 
859         assertEquals("Search results should be empty.", 0, results.getSearchResults().size());
860 
861         criteria = DocumentSearchCriteria.Builder.create();
862         criteria.setDocumentTypeName(documentTypeName);
863         addSearchableAttribute(criteria, "fakeproperty", "doesntexist");
864         try {
865             results =  docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
866             fail("Search results should be throwing a validation exception for use of non-existant searchable attribute");
867         } catch (RuntimeException wsee) {
868             assertTrue(wsee.getMessage().contains("LookupException"));
869         }
870     }
871 
872 
873     /**
874      * Tests the resolution to issues EN-95, KULWF-757, KULOWF-52 whereby the use of a quickfinder is causing
875      * NullPointers when searching for documents.
876      */
877     @Test public void testSearchableAttributeWithQuickfinder() throws Exception {
878         String documentTypeName = "AttributeWithQuickfinderDocType";
879         String key = "chart";
880         DocumentType docType = ((DocumentTypeService)KEWServiceLocator.getService(KEWServiceLocator.DOCUMENT_TYPE_SERVICE)).findByName(documentTypeName);
881          WorkflowDocument document = WorkflowDocumentFactory.createDocument(getPrincipalIdForName("rkirkend"), documentTypeName);
882 
883          // define the chart for the searchable attribute
884          WorkflowAttributeDefinition.Builder chartDef = WorkflowAttributeDefinition.Builder.create("SearchableAttributeWithQuickfinder");
885          chartDef.addPropertyDefinition(key, "BL");
886          document.addSearchableDefinition(chartDef.build());
887 
888          // save the document
889          document.setTitle("Routin' with style");
890          document.saveDocument("Savin' this document.");
891 
892          // prepare to search
893          DocumentSearchService docSearchService = (DocumentSearchService) KEWServiceLocator.getService(KEWServiceLocator.DOCUMENT_SEARCH_SERVICE);
894          Person user = KimApiServiceLocator.getPersonService().getPersonByPrincipalName("rkirkend");
895 
896          // execute the search by our chart, we should see one result
897          DocumentSearchCriteria.Builder criteria = DocumentSearchCriteria.Builder.create();
898          criteria.setDocumentTypeName(documentTypeName);
899          addSearchableAttribute(criteria, key, "BL");
900          DocumentSearchResults results = docSearchService.lookupDocuments(user.getPrincipalId(),
901                  criteria.build());
902          assertEquals("Search results should have one document.", 1, results.getSearchResults().size());
903          DocumentSearchResult result = results.getSearchResults().get(0);
904          String documentId = result.getDocument().getDocumentId();
905          assertEquals("Wrong document in search results.", document.getDocumentId(), documentId);
906 
907          // search with no searchable attribute criteria, should return our document as well
908          criteria = DocumentSearchCriteria.Builder.create();
909          criteria.setDocumentTypeName(documentTypeName);
910          results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
911          assertEquals("Search results should have one document.", 1, results.getSearchResults().size());
912          result = results.getSearchResults().get(0);
913          assertEquals("Wrong document in search results.", document.getDocumentId(), result.getDocument().getDocumentId());
914 
915     }
916 
917     /**
918      * Tests that the hiding of fields and columns works properly to resolve EN-53.
919      *
920      * TODO this is currently commented out because we can't test this properly through the unit
921      * test since the filtering of the column actually happens in the web-tier.  Shoudl this be
922      * the case?  Maybe we need to re-examine when we refactor document search.
923      */
924     @Ignore // can't test this properly through the unit test since the filtering of the column actually happens in the web-tier
925     @Test public void testSearchableAttributeWithHiddens() throws Exception {
926         // for the following document, the chart field should not show up in the result set and the org field
927         // should not show up in the criteriaw
928         String docType = "AttributeWithHiddensDocType";
929         DocumentType documentType = ((DocumentTypeService)KEWServiceLocator.getService(KEWServiceLocator.DOCUMENT_TYPE_SERVICE)).findByName(docType);
930 
931         String attributeName = "SearchableAttributeWithHiddens";
932         WorkflowDocument document = WorkflowDocumentFactory.createDocument(getPrincipalIdForName("rkirkend"), docType);
933 
934         // define the chart for the searchable attribute
935         WorkflowAttributeDefinition.Builder chartDef = WorkflowAttributeDefinition.Builder.create(attributeName);
936         chartDef.addPropertyDefinition("chart", "BL");
937         chartDef.addPropertyDefinition("org", "ARSC");
938         chartDef.addPropertyDefinition("dollar", "24");
939         document.addSearchableDefinition(chartDef.build());
940 
941         // save the document
942         document.setTitle("Routin' with style");
943         document.saveDocument("Savin' this document.");
944 
945         // prepare to search
946         DocumentSearchService docSearchService = (DocumentSearchService) KEWServiceLocator.getService(KEWServiceLocator.DOCUMENT_SEARCH_SERVICE);
947         Person user = KimApiServiceLocator.getPersonService().getPersonByPrincipalName("rkirkend");
948 
949         // execute the search by our chart, we should see one result
950         DocumentSearchCriteria.Builder criteria = DocumentSearchCriteria.Builder.create();
951         criteria.setDocumentTypeName(docType);
952         addSearchableAttribute(criteria, "chart", "BL");
953         DocumentSearchResults results = docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
954         assertEquals("Search results should have one document.", 1, results.getSearchResults().size());
955         DocumentSearchResult result = results.getSearchResults().get(0);
956         assertEquals("Wrong document in search results.", document.getDocumentId(), result.getDocument().getDocumentId());
957         // also check that the chart field is not in the result set and the org field is
958         DocumentAttribute documentAttribute = result.getSingleDocumentAttributeByName("chart");
959         assertNull("The chart column should not be in the result set!", documentAttribute);
960         documentAttribute = result.getSingleDocumentAttributeByName("org");
961         assertNotNull("The org column should be in the result set", documentAttribute);
962         assertEquals("Wrong org code.", "ARSC", documentAttribute.getValue());
963         documentAttribute = result.getSingleDocumentAttributeByName("dollar");
964         assertNotNull("The dollar column should be in the result set", documentAttribute);
965         assertEquals("Wrong dollar code.", "24", documentAttribute.getValue().toString());
966     }
967 
968     @Test public void testSetApplicationContentXMLRoutedDocument() throws Exception {
969         String documentTypeName = "SearchDocType";
970         String key = "givenname";
971         DocumentType docType = ((DocumentTypeService)KEWServiceLocator.getService(KEWServiceLocator.DOCUMENT_TYPE_SERVICE)).findByName(documentTypeName);
972         WorkflowDocument workflowDocument = WorkflowDocumentFactory.createDocument(getPrincipalIdForName("rkirkend"), documentTypeName);
973         workflowDocument.setApplicationContent("<documentContent><searchableContent><putWhateverWordsIwantInsideThisTag>" +
974                                                "<givenname><value>jack</value></givenname>" +
975                                                "</putWhateverWordsIwantInsideThisTag></searchableContent></documentContent>");
976 
977         workflowDocument.setTitle("Routing style");
978         workflowDocument.route("routing this document.");
979 
980         DocumentSearchService docSearchService = (DocumentSearchService) KEWServiceLocator.getService(KEWServiceLocator.DOCUMENT_SEARCH_SERVICE);
981 
982         Person user = KimApiServiceLocator.getPersonService().getPersonByPrincipalName("rkirkend");
983         DocumentSearchCriteria.Builder criteria = DocumentSearchCriteria.Builder.create();
984         criteria.setDocumentTypeName(documentTypeName);
985         addSearchableAttribute(criteria, key, "jack");
986         DocumentSearchResults results =  docSearchService.lookupDocuments(user.getPrincipalId(),
987                 criteria.build());
988 
989         assertEquals("Search results should be empty.", 1, results.getSearchResults().size());
990 
991         criteria = DocumentSearchCriteria.Builder.create();
992         criteria.setDocumentTypeName(documentTypeName);
993         addSearchableAttribute(criteria, key, "fred");
994         results =  docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
995 
996         assertEquals("Search results should be empty.", 0, results.getSearchResults().size());
997 
998         criteria = DocumentSearchCriteria.Builder.create();
999         criteria.setDocumentTypeName(documentTypeName);
1000         addSearchableAttribute(criteria, "fakeproperty", "doesntexist");
1001         try {
1002             results =  docSearchService.lookupDocuments(user.getPrincipalId(), criteria.build());
1003             fail("Search results should be throwing a validation exception for use of non-existant searchable attribute");
1004         } catch (RuntimeException wsee) {
1005             assertTrue(wsee.getMessage().contains("LookupException"));
1006         }
1007     }
1008 
1009     /**
1010      * Tests that Field objects use correct KeyValue instances when checks for blank valid values are performed
1011      * (such as when JSP renders drop-downs), to verify that KULRICE-3587 has been fixed.
1012      * 
1013      * @throws Exception
1014      */
1015     @Test public void testBlankValidValuesOnKeyValues() throws Exception {
1016         boolean[] shouldHaveBlank = {true, false};
1017         String[] attributesToTest = {"XMLSearchableAttributeWithBlank", "XMLSearchableAttributeWithoutBlank"};
1018 
1019         // Verify that the getHasBlankValidValue() method on each field returns the correct result and does not cause unexpected exceptions.
1020         for (int i = 0; i < shouldHaveBlank.length; i++) {
1021             ExtensionDefinition ed = createExtensionDefinition(attributesToTest[i]);
1022             List<RemotableAttributeField> remotableAttributeFields = getAttribute(attributesToTest[i]).getSearchFields(ed, "BlankValidValuesDocType");
1023             List<Row> rowList = FieldUtils.convertRemotableAttributeFields(remotableAttributeFields);
1024             assertEquals("The searching fields for " + attributesToTest[i] + " should have exactly one element", 1, rowList.size());
1025             assertEquals("Searching row for " + attributesToTest[i] + " should have exactly one field", 1, rowList.get(0).getFields().size());
1026 
1027             Field testField = rowList.get(0).getFields().get(0);
1028             try {
1029                 assertEquals("The field for " + attributesToTest[i] + " does not have the expected getHasBlankValidValue() result",
1030                         shouldHaveBlank[i], testField.getHasBlankValidValue());
1031             } catch (Exception ex) {
1032                 fail("An exception occurred while running getHasBlankValidValue() on " + attributesToTest[i] + ": " + ex.getMessage());
1033             }
1034         }
1035     }
1036 }