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