001    /**
002     * Copyright 2005-2013 The Kuali Foundation
003     *
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.opensource.org/licenses/ecl2.php
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.kuali.rice.kew.docsearch.xml;
017    
018    import org.joda.time.DateMidnight;
019    import org.joda.time.DateTime;
020    import org.junit.Ignore;
021    import org.junit.Test;
022    import org.kuali.rice.core.api.uif.RemotableAttributeError;
023    import org.kuali.rice.core.api.uif.RemotableAttributeField;
024    import org.kuali.rice.core.api.util.RiceConstants;
025    import org.kuali.rice.kew.api.KewApiConstants;
026    import org.kuali.rice.kew.api.WorkflowDocument;
027    import org.kuali.rice.kew.api.WorkflowDocumentFactory;
028    import org.kuali.rice.kew.api.document.Document;
029    import org.kuali.rice.kew.api.document.DocumentContent;
030    import org.kuali.rice.kew.api.document.DocumentWithContent;
031    import org.kuali.rice.kew.api.document.attribute.DocumentAttribute;
032    import org.kuali.rice.kew.api.document.attribute.WorkflowAttributeDefinition;
033    import org.kuali.rice.kew.api.document.search.DocumentSearchCriteria;
034    import org.kuali.rice.kew.api.document.search.DocumentSearchResult;
035    import org.kuali.rice.kew.api.document.search.DocumentSearchResults;
036    import org.kuali.rice.kew.api.exception.WorkflowException;
037    import org.kuali.rice.kew.api.extension.ExtensionDefinition;
038    import org.kuali.rice.kew.docsearch.DocumentSearchInternalUtils;
039    import org.kuali.rice.kew.docsearch.DocumentSearchTestBase;
040    import org.kuali.rice.kew.docsearch.SearchableAttributeLongValue;
041    import org.kuali.rice.kew.docsearch.TestXMLSearchableAttributeDateTime;
042    import org.kuali.rice.kew.docsearch.TestXMLSearchableAttributeFloat;
043    import org.kuali.rice.kew.docsearch.TestXMLSearchableAttributeLong;
044    import org.kuali.rice.kew.docsearch.TestXMLSearchableAttributeString;
045    import org.kuali.rice.kew.docsearch.service.DocumentSearchService;
046    import org.kuali.rice.kew.doctype.bo.DocumentType;
047    import org.kuali.rice.kew.doctype.service.DocumentTypeService;
048    import org.kuali.rice.kew.exception.WorkflowServiceErrorException;
049    import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue;
050    import org.kuali.rice.kew.service.KEWServiceLocator;
051    import org.kuali.rice.kew.test.TestUtilities;
052    import org.kuali.rice.kim.api.identity.Person;
053    import org.kuali.rice.kim.api.services.KimApiServiceLocator;
054    import org.kuali.rice.kns.util.FieldUtils;
055    import org.kuali.rice.kns.web.ui.Field;
056    import org.kuali.rice.kns.web.ui.Row;
057    import org.kuali.rice.test.BaselineTestCase;
058    import org.w3c.dom.Element;
059    import org.xml.sax.InputSource;
060    
061    import javax.xml.parsers.DocumentBuilderFactory;
062    import javax.xml.xpath.XPath;
063    import javax.xml.xpath.XPathConstants;
064    import javax.xml.xpath.XPathFactory;
065    import java.io.BufferedReader;
066    import java.io.StringReader;
067    import java.math.BigDecimal;
068    import java.math.BigInteger;
069    import java.sql.Timestamp;
070    import java.text.ParseException;
071    import java.util.Date;
072    import java.util.List;
073    
074    import static org.junit.Assert.*;
075    import static org.junit.Assert.assertEquals;
076    
077    /**
078     * Tests the StandardGenericXMLSearchableAttribute.
079     *
080     * KULWF-654: Tests the resolution to this issue by configuring a CustomActionListAttribute as well as a
081     * searchable attribute.
082     */
083    @BaselineTestCase.BaselineMode(BaselineTestCase.Mode.NONE)
084    public class StandardGenericXMLSearchableAttributeTest extends DocumentSearchTestBase {
085    
086        protected void loadTestData() throws Exception {
087            loadXmlFile("XmlConfig.xml");
088        }
089    
090        @Test public void testXMLStandardSearchableAttributeWithInvalidValue() throws Exception {
091            String documentTypeName = "SearchDocTypeStandardSearchDataType";
092            String userNetworkId = "rkirkend";
093            WorkflowDocument workflowDocument = WorkflowDocumentFactory.createDocument(getPrincipalIdForName(userNetworkId), documentTypeName);
094    
095            /*
096             *   Below we are using the keys and values from the custom searchable attribute classes' static constants but
097             *   this is only for convenience as those should always be valid values to test for.
098             */
099            // 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    }