001/**
002 * Copyright 2005-2014 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 */
016package org.kuali.rice.kns.workflow;
017
018import org.apache.commons.lang.exception.ExceptionUtils;
019import org.junit.Assert;
020import org.junit.Test;
021import org.kuali.rice.core.api.uif.RemotableAttributeError;
022import org.kuali.rice.core.api.util.type.KualiDecimal;
023import org.kuali.rice.kew.api.document.search.DocumentSearchCriteria;
024import org.kuali.rice.kew.api.document.search.DocumentSearchResults;
025import org.kuali.rice.kew.api.exception.WorkflowException;
026import org.kuali.rice.kew.docsearch.service.DocumentSearchService;
027import org.kuali.rice.kew.doctype.bo.DocumentType;
028import org.kuali.rice.kew.service.KEWServiceLocator;
029import org.kuali.rice.kim.api.services.KimApiServiceLocator;
030import org.kuali.rice.krad.UserSession;
031import org.kuali.rice.krad.datadictionary.exception.UnknownDocumentTypeException;
032import org.kuali.rice.krad.service.DocumentService;
033import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
034import org.kuali.rice.krad.test.document.AccountWithDDAttributesDocument;
035import org.kuali.rice.krad.util.GlobalVariables;
036import org.kuali.rice.kns.workflow.attribute.DataDictionarySearchableAttribute;
037import org.kuali.rice.krad.test.KRADTestCase;
038
039import java.sql.Timestamp;
040import java.util.ArrayList;
041import java.util.Calendar;
042import java.util.Collections;
043import java.util.HashMap;
044import java.util.List;
045import java.util.Map;
046import java.util.concurrent.Callable;
047
048import static junit.framework.Assert.assertEquals;
049import static junit.framework.Assert.fail;
050
051/**
052 * DataDictionarySearchableAttributeTest performs various DataDictionarySearchableAttribute-related tests on the doc search, including verification of proper wildcard functionality
053 * 
054 * @author Kuali Rice Team (rice.collab@kuali.org)
055 *
056 * @deprecated KNS test class, convert to KRAD equivalent if applicable.
057 */
058@Deprecated
059public class DataDictionarySearchableAttributeTest extends KRADTestCase {
060
061    @Override
062    public void setUp() throws Exception {
063        super.setUp();
064        GlobalVariables.setUserSession(new UserSession("quickstart"));
065    }
066    
067    private final static String ACCOUNT_WITH_DD_ATTRIBUTES_DOCUMENT_NAME = "AccountWithDDAttributes";
068    
069    enum DOCUMENT_FIXTURE {
070        NORMAL_DOCUMENT("Testing NORMAL_DOCUMENT", new Integer(1234567890), "John Doe", new KualiDecimal(501.77), createTimestamp(
071                2009, Calendar.OCTOBER, 15, 0, 0, 0), createTimestamp(2009, Calendar.NOVEMBER, 1, 0, 0, 0), "SecondState", true),
072        ZERO_NUMBER_DOCUMENT("Testing ZERO_NUMBER_DOCUMENT", new Integer(0), "Jane Doe", new KualiDecimal(-100), createTimestamp(
073                2009, Calendar.OCTOBER, 16, 0, 0, 0), createTimestamp(2015, Calendar.NOVEMBER, 2, 0, 0, 0), "FirstState", true),
074        FALSE_AWAKE_DOCUMENT("Testing FALSE_AWAKE_DOCUMENT", new Integer(987654321), "John D'oh", new KualiDecimal(0.0), createTimestamp(
075                2006, Calendar.OCTOBER, 17, 0, 0, 0), createTimestamp(1900, Calendar.NOVEMBER, 3, 0, 0, 0), "FourthState", false),
076        ODD_NAME_DOCUMENT("Testing ODD_NAME_DOCUMENT", new Integer(88), "_", new KualiDecimal(10000051.0), createTimestamp(
077                2009, Calendar.OCTOBER, 18, 0, 0, 0), createTimestamp(2009, Calendar.NOVEMBER, 4, 0, 0, 0), "FourthState", true),
078        ODD_TIMESTAMP_DOCUMENT("Testing ODD_TIMESTAMP_DOCUMENT", new Integer(9000), "Shane Kloe", new KualiDecimal(4.54), createTimestamp(
079                2012, Calendar.OCTOBER, 19, 0, 0, 0), createTimestamp(2007, Calendar.NOVEMBER, 5, 12, 4, 38), "ThirdState", false),
080        ANOTHER_ODD_NAME_DOCUMENT("Testing ANOTHER_ODD_NAME_DOCUMENT", new Integer(1234567889), "---", new KualiDecimal(501), createTimestamp(
081                2009, Calendar.APRIL, 20, 0, 0, 0), createTimestamp(2009, Calendar.NOVEMBER, 6, 12, 59, 59), "ThirdState", true),
082        INVALID_STATE_DOCUMENT("Testing INVALID_STATE_DOCUMENT", new Integer(99999), "AAAAAAAAA", new KualiDecimal(2.22), createTimestamp(
083                2009, Calendar.OCTOBER, 21, 0, 0, 0), createTimestamp(2009, Calendar.NOVEMBER, 7, 0, 0, 1), "SeventhState", true),
084        WILDCARD_NAME_DOCUMENT("Testing WILDCARD_NAME_DOCUMENT", new Integer(1), "Sh*ne><K!=e?", new KualiDecimal(771.05), createTimestamp(
085                2054, Calendar.OCTOBER, 22, 0, 0, 0), createTimestamp(2008, Calendar.NOVEMBER, 8, 12, 0, 0), "FirstState", true);
086        
087        private String accountDocumentDescription;
088        private Integer accountNumber;
089        private String accountOwner;
090        private KualiDecimal accountBalance;
091        private Timestamp accountOpenDate;
092        private Timestamp accountUpdateDateTime;
093        private String accountState;
094        private boolean accountAwake;
095        
096        private DOCUMENT_FIXTURE(String accountDocumentDescription, Integer accountNumber, String accountOwner, KualiDecimal accountBalance, Timestamp accountOpenDate, Timestamp accountUpdateDateTime, String accountState, boolean accountAwake) {
097                this.accountDocumentDescription = accountDocumentDescription;
098                this.accountNumber = accountNumber;
099                this.accountOwner = accountOwner;
100                this.accountBalance = accountBalance;
101                this.accountOpenDate = accountOpenDate;
102                this.accountUpdateDateTime = accountUpdateDateTime;
103                this.accountState = accountState;
104                this.accountAwake = accountAwake;
105        }
106        
107        public AccountWithDDAttributesDocument getDocument(DocumentService docService) throws WorkflowException {
108                AccountWithDDAttributesDocument acctDoc = (AccountWithDDAttributesDocument) docService.getNewDocument(ACCOUNT_WITH_DD_ATTRIBUTES_DOCUMENT_NAME);
109                acctDoc.getDocumentHeader().setDocumentDescription(this.accountDocumentDescription);
110                acctDoc.setAccountNumber(this.accountNumber);
111                acctDoc.setAccountOwner(this.accountOwner);
112                acctDoc.setAccountBalance(this.accountBalance);
113                acctDoc.setAccountOpenDate(this.accountOpenDate);
114                acctDoc.setAccountUpdateDateTime(this.accountUpdateDateTime);
115                acctDoc.setAccountState(this.accountState);
116                acctDoc.setAccountAwake(this.accountAwake);
117                
118                return acctDoc;
119        }
120    }
121        
122        /**
123         * Tests the use of multi-select and wildcard searches to ensure that they function correctly for DD searchable attributes on the doc search.
124         */
125    @Test
126        public void testWildcardsAndMultiSelectsOnDDSearchableAttributes() throws Exception {
127                DocumentService docService = KRADServiceLocatorWeb.getDocumentService();
128                //docSearchService = KEWServiceLocator.getDocumentSearchService();
129                DocumentType docType = KEWServiceLocator.getDocumentTypeService().findByName("AccountWithDDAttributes");
130        String principalName = "quickstart";
131        String principalId = KimApiServiceLocator.getPersonService().getPersonByPrincipalName(principalName).getPrincipalId();
132                
133        // Route some test documents.
134                docService.routeDocument(DOCUMENT_FIXTURE.NORMAL_DOCUMENT.getDocument(docService), "Routing NORMAL_DOCUMENT", null);
135                docService.routeDocument(DOCUMENT_FIXTURE.ZERO_NUMBER_DOCUMENT.getDocument(docService), "Routing ZERO_NUMBER_DOCUMENT", null);
136                docService.routeDocument(DOCUMENT_FIXTURE.FALSE_AWAKE_DOCUMENT.getDocument(docService), "Routing FALSE_AWAKE_DOCUMENT", null);
137                docService.routeDocument(DOCUMENT_FIXTURE.ODD_NAME_DOCUMENT.getDocument(docService), "Routing ODD_NAME_DOCUMENT", null);
138                docService.routeDocument(DOCUMENT_FIXTURE.ODD_TIMESTAMP_DOCUMENT.getDocument(docService), "Routing ODD_TIMESTAMP_DOCUMENT", null);
139                docService.routeDocument(DOCUMENT_FIXTURE.ANOTHER_ODD_NAME_DOCUMENT.getDocument(docService), "Routing ANOTHER_ODD_NAME_DOCUMENT", null);
140                docService.routeDocument(DOCUMENT_FIXTURE.INVALID_STATE_DOCUMENT.getDocument(docService), "Routing INVALID_STATE_DOCUMENT", null);
141                docService.routeDocument(DOCUMENT_FIXTURE.WILDCARD_NAME_DOCUMENT.getDocument(docService), "Routing WILDCARD_NAME_DOCUMENT", null);
142
143                // Ensure that DD searchable attribute integer fields function correctly when searched on.
144                // Note that negative numbers are disallowed by the NumericValidationPattern that validates this field.
145                assertDDSearchableAttributeWildcardsWork(docType, principalId, "accountNumber",
146                                new String[] {"!1234567890", "9???9", ">1", "987654321|1234567889", "<100", ">=99999", "<=-42", ">9000|<=1", "<1|>=1234567890",
147                                                ">1234567889&&<1234567890", ">=88&&<=99999", "0|>10&&<10000", "9000..1000000", "0..100|>1234567889", "1..10000&&>50", "250..50"},
148                                new int[]    {7            , -1     , 6   , 2                     , 3     , 4        , -1     , 6          , 2,
149                                                0                         , 3              , 3              , 2              , 4                   , 2              , 0});
150                
151                // Ensure that DD searchable attribute string fields function correctly when searched on.
152                // Note that DD searchable attributes cannot treat wildcards literally, so the "Sh*ne><K!=e" case below yields very different results.
153                assertDDSearchableAttributeWildcardsWork(docType, principalId, "accountOwner",
154                                new String[] {"!John Doe", "!John*", "!John Doe&&!Shane Kloe", "!Jane ???", "!Jane Doe!John Doe", "_", "_|---", "Sh*ne><K!=e",
155                                                ">Jane Doe", "<Shane Kloe", ">=Johnny", "<=John D'oh", ">John Doe|<---", ">=AAAAAAAAA&&<=Jane Doe", ">---&&!John D'oh",
156                                                "<Shane Kloe&&!John*", "*oe", "???? Doe", "Jane Doe..John Doe", "AAAAAAAAA..Shane Kloe&&!John Doe", "John D'oh|---..Jane Doe"},
157                                new int[]    {7          , 6       , 6                       , 7          , 6                   , 1  , 2      , 8,
158                                                5          , 6            , 3         , 4            , 3               , 2                        , 6,
159                                                4                    , 3    , 2         , 3                   , 5                                 , 4});
160                
161                // Ensure that DD searchable attribute float fields function correctly when searched on. Also ensure that the CurrencyFormatter is working.
162                assertDDSearchableAttributeWildcardsWork(docType, principalId, "accountBalance",
163                                new String[] {"501.??", "*.54" , "!2.22", "10000051.0|771.05", "<0.0", ">501", "<=4.54", ">=-99.99", ">4.54|<=-1", ">=0&&<501.77",
164                                                "<=0|>=10000051", ">501&&<501.77", "-100|>771.05", "2.22..501", "-100..4.54&&<=0", "2.22|501.77..10000051.0", "Zero",
165                                                "-$100", "<(501)&&>=($2.22)", "$4.54|<(1)", "($0.00)..$771.05", ">=$(500)", ")501(", "4.54$", "$501..0"},
166                                new int[]    {-1      , -1     , 7      , 2                  , 1     , 3     , 4       , 7         , 5           , 4             ,
167                                                3               , 0              , 2             , 3          , 2                , 4                        , -1,
168                                                1      , 2                  , 3           , 6                 , -1        , -1     , -1     , 0});
169                
170                // Ensure that DD searchable attribute date fields function correctly when searched on.
171                // Note that dates with non-two-digit years outside the range of 1000 to 9999 will now fail validation.
172                assertDDSearchableAttributeWildcardsWork(docType, principalId, "accountOpenDate",
173                                new String[] {"!10/15/2009", "Unknown", "10/15/2009|10/21/2009", "10/22/????", "*/*/05", ">10/17/06", "<=12-31-09&&>=10/16/2009",
174                                                ">101809&&<102012", ">=10/22/2054|<10/16/2009", ">2-29-12|<=10/21/09", "<2009", ">=10/19/2012|04/20/09", ">2/29/09", "2009..2008",
175                                                "10/15/2009..10/21/2009", "1/1/2009..10/20/2009|10/22/2054", "<=06/32/03", ">2008&&<2011|10/17/06", 
176                                                "<02/26/10500", ">05-07-333", ">=03/26/1001", "<=11-11-9900"},
177                                new int[]    {-1           , -1       , 2                      , -1          , -1      , 7          , 3,
178                                                2                 , 4                         , 8                    , 1      , 3                      , -1        , -1,
179                                                4                       , 5                                , -1          , 6                      ,
180                                                -1            , -1          , 8             , 8});
181                
182                // Ensure that DD searchable attribute multi-select fields function correctly when searched on.
183                // Currently, an exception is *not* thrown if the value given is not among the selectable values.
184                assertDDSearchableAttributeWildcardsWork(docType, principalId, "accountStateMultiselect",
185                                new String[][] {{"FirstState"}, {"SecondState"}, {"ThirdState"}, {"FourthState"}, {"FirstState","ThirdState"},
186                                                {"SecondState","FourthState"}, {"ThirdState","SecondState"}, {"FourthState","FirstState","SecondState"}, {"SeventhState"},
187                                                {"ThirdState","FirstState","SecondState","FourthState"}},
188                                new int[]      {2             , 1              , 2             , 2              , 4,
189                                                3                            , 3                           , 5                                         , 1,
190                                                7});
191                
192                // Ensure that DD searchable attribute boolean fields function correctly when searched on.
193                // TODO: Add the commented-out boolean search expressions back in once KULRICE-3698 is complete.
194                assertDDSearchableAttributeWildcardsWork(docType, principalId, "accountAwake", new String[] {"Y", "N"}, new int[] {6, 2});
195                /*assertDDSearchableAttributeWildcardsWork(docType, principalId, "accountAwake",
196                                new String[] {"Y", "N", "Z", "Neither", "n", "y", "true", "FALSE", "fAlSe", "TrUe", "NO", "Yes", "f", "F", "T", "t", "2", "0", "1",
197                                                "Active", "INACTIVE", "On", "Off", "ON", "off", "EnAbLeD", "enabled", "dIsAbLeD", "DISABLED"},
198                                new int[]    {6  , 2  , -1 , -1       , 2  , 6  , 6     , 2      , 2      , 6     , 2   , 6    , 2  , 2  , 6  , 6  , -1 , 2  , 6  ,
199                                                -1      , -1        , 6   , 2    , 6   , 2    , 6        , 6        , 2         , 2});*/
200                
201                // Ensure that DD searchable attribute timestamp fields function correctly when searched on.
202                // Note that timestamps with non-two-digit years outside the range of 1000 to 9999 will now fail validation.
203                assertDDSearchableAttributeWildcardsWork(docType, principalId, "accountUpdateDateTime",
204                                new String[] {"!11/01/2009 00:00:00",  "11/02/2015 00:00:00|11/06/2009 12:59:59", "11/??/2009 ??:??:??", ">110609 12:59:59",
205                                                "<=2009 1:2:3", ">=11/06/09 12:59:59", "<11/8/2008 12:00 PM", "Blank",
206                                                "11/3/1900 00:00:00|>11-7-09 00:00:01", "02/29/2008 07:00:00..11/04/2009 00:00:00",
207                                                "11/1/09 00:00:00..11/06/09 12:59:59|11/03/1900 00:00:00", "2009..2008", "2000..2009&&>=110507 12:4:38",
208                                                "<=11/08/2008 12:00 AM", ">=01-01-1000 00:00:00", ">12/31/999 23:59:59", "<01-01-10000 00:00:00", "<=12/31/9999 23:59:59"},
209                                new int[]    {-1                    , 2                                        , -1                   , 2,
210                                                3             , 3                    , 2                    , -1,
211                                                2                                     , 3,
212                                                4                                                    , -1          , 2,
213                                                2                      , 8                      , -1                   , -1                     , 8});
214        }
215
216    /**
217     * Utility method to create a timestamp quickly
218     * 
219     * @param year the year of the timestamp
220     * @param month the month of the timestamp
221     * @param day the day of the timestamp
222     * @param hour the hour of the timestamp
223     * @param minute the minute of the timestamp
224     * @param second the second of the timestamp
225     * @return a new java.sql.Timestamp initialized to the precise time given
226     */
227    private static Timestamp createTimestamp(int year, int month, int day, int hour, int minute, int second) {
228        Calendar date = Calendar.getInstance();
229        date.set(year, month, day, hour, minute, second);
230        return new java.sql.Timestamp(date.getTimeInMillis());
231    }
232
233    /**
234     * A convenience method for testing wildcards on data dictionary searchable attributes
235     *
236     * @param docType The document type containing the attributes.
237     * @param principalId The ID of the user performing the search.
238     * @param fieldName The name of the field on the test document.
239     * @param searchValues The search expressions to test. Has to be a String array (for regular fields) or a String[] array (for multi-select fields).
240     * @param resultSizes The number of expected documents to be returned by the search; use -1 to indicate that an error should have occurred.
241     * @throws Exception
242     */
243    private void assertDDSearchableAttributeWildcardsWork(DocumentType docType, String principalId, String fieldName, Object[] searchValues,
244                int[] resultSizes) throws Exception {
245        if (!(searchValues instanceof String[]) && !(searchValues instanceof String[][])) {
246                throw new IllegalArgumentException("'searchValues' parameter has to be either a String[] or a String[][]");
247        }
248        DocumentSearchCriteria.Builder criteria = null;
249        DocumentSearchResults results = null;
250        DocumentSearchService docSearchService = KEWServiceLocator.getDocumentSearchService();
251        for (int i = 0; i < resultSizes.length; i++) {
252                criteria = DocumentSearchCriteria.Builder.create();
253                criteria.setDocumentTypeName(docType.getName());
254            if (searchValues instanceof String[][]) {
255                String[] innerArray = (String[]) searchValues[i];
256                for (int j=0; j<innerArray.length; j++) {
257                    criteria.addDocumentAttributeValue(fieldName, innerArray[j]);
258                }
259            } else {
260                criteria.addDocumentAttributeValue(fieldName, searchValues[i].toString());
261            }
262
263                try {
264                        results = docSearchService.lookupDocuments(principalId, criteria.build());
265                        if (resultSizes[i] < 0) {
266                                Assert.fail(fieldName + "'s search at loop index " + i + " should have thrown an exception");
267                        }
268                        if(resultSizes[i] != results.getSearchResults().size()){
269                                assertEquals(fieldName + "'s search results at loop index " + i + " returned the wrong number of documents.", resultSizes[i], results.getSearchResults().size());
270                        }
271                } catch (Exception ex) {
272                        if (resultSizes[i] >= 0) {
273                    LOG.error("exception", ex);
274                                Assert.fail(fieldName
275                            + "'s search at loop index "
276                            + i
277                            + " for search value '"
278                            + searchValues[i]
279                            + "' should not have thrown an exception");
280                        }
281                }
282                GlobalVariables.clear();
283        }
284    }
285    
286    /**
287     * Validates that the search inputs does not cause a class cast exception
288     */
289    @Test
290    public void testValidateUserSearchInputsNoCast() throws Exception {
291        DataDictionarySearchableAttribute searchableAttribute = new DataDictionarySearchableAttribute();
292        final DocumentService documentService = KRADServiceLocatorWeb.getDocumentService();
293
294        try {
295            AccountWithDDAttributesDocument document = DOCUMENT_FIXTURE.NORMAL_DOCUMENT.getDocument(documentService);
296            documentService.saveDocument(document);
297            final String documentNumber = document.getDocumentNumber();
298        } catch (UnknownDocumentTypeException udte) {
299            fail("CI failure - https://jira.kuali.org/browse/KULRICE-9289 " + udte.getMessage() + ExceptionUtils.getStackTrace(udte));
300        }
301
302        Exception caughtException;
303        List foundErrors;
304        
305        caughtException = null;
306        foundErrors = new ArrayList();
307        DocumentSearchCriteria.Builder criteria = DocumentSearchCriteria.Builder.create();
308        criteria.setDocumentTypeName(ACCOUNT_WITH_DD_ATTRIBUTES_DOCUMENT_NAME);
309        Map<String, List<String>> simpleParamMap = new HashMap<String, List<String>>();
310        simpleParamMap.put("accountState", Collections.singletonList("FirstState"));
311        criteria.setDocumentAttributeValues(simpleParamMap);
312        try {
313                foundErrors = searchableAttribute.validateDocumentAttributeCriteria(null, criteria.build());
314        } catch (RuntimeException re) {
315                caughtException = re;
316        }
317        Assert.assertNull("Found Exception " + caughtException, caughtException);
318        Assert.assertTrue("There were errors: " + foundErrors, (foundErrors == null || foundErrors.isEmpty()));
319        
320        caughtException = null;
321        foundErrors = new ArrayList();
322        Map<String, List<String>>  listParamMap = new HashMap<String, List<String>>();
323        List<String> paramValues = new ArrayList<String>();
324        paramValues.add("FirstState");
325        paramValues.add("SecondState");
326        listParamMap.put("accountState", paramValues);
327        criteria.setDocumentAttributeValues(listParamMap);
328        try {
329                foundErrors = searchableAttribute.validateDocumentAttributeCriteria(null, criteria.build());
330        } catch (RuntimeException re) {
331                caughtException = re;
332        }
333        Assert.assertNull("Found Exception " + caughtException, caughtException);
334        Assert.assertTrue("There were errors: " + foundErrors, (foundErrors == null || foundErrors.isEmpty()));
335    }
336
337    /**
338     * Tests handling resolution of error messages
339     */
340    @Test
341    public void testErrorMessageResolution() throws Exception {
342        final DataDictionarySearchableAttribute searchableAttribute = new DataDictionarySearchableAttribute();
343        final DocumentSearchCriteria.Builder criteria = DocumentSearchCriteria.Builder.create();
344        /*criteria.setDocumentTypeName(ACCOUNT_WITH_DD_ATTRIBUTES_DOCUMENT_NAME);
345        Map<String, List<String>> simpleParamMap = new HashMap<String, List<String>>();
346        simpleParamMap.put("accountState", Collections.singletonList("FirstState"));
347        criteria.setDocumentAttributeValues(simpleParamMap);*/
348        List<RemotableAttributeError> errors = GlobalVariables.doInNewGlobalVariables(new Callable<List<RemotableAttributeError>>() {
349            public List<RemotableAttributeError> call() {
350                GlobalVariables.getMessageMap().putError("fake.property", "error.custom", "the error message");
351                return searchableAttribute.validateDocumentAttributeCriteria(null, criteria.build());
352            }
353        });
354        Assert.assertEquals(1, errors.size());
355        assertEquals("the error message", errors.get(0).getMessage());
356    }
357
358    /**
359     * Test multiple value searches in the context of whole document search context
360     */
361    @Test
362    public void testMultiSelectIntegration() throws Exception {
363        final DocumentService docService = KRADServiceLocatorWeb.getDocumentService();
364                //docSearchService = KEWServiceLocator.getDocumentSearchService();
365                DocumentType docType = KEWServiceLocator.getDocumentTypeService().findByName("AccountWithDDAttributes");
366        String principalName = "quickstart";
367        String principalId = KimApiServiceLocator.getPersonService().getPersonByPrincipalName(principalName).getPrincipalId();
368                
369        // Route some test documents.
370                docService.routeDocument(DOCUMENT_FIXTURE.NORMAL_DOCUMENT.getDocument(docService), "Routing NORMAL_DOCUMENT", null);
371                
372                assertDDSearchableAttributeWildcardsWork(docType, principalId, "accountStateMultiselect",
373                                new String[][] {{"FirstState"}, {"SecondState"}, {"ThirdState"}, {"FourthState"}, {"FirstState", "SecondState"}, {"FirstState","ThirdState"}, {"FirstState", "FourthState"}, {"SecondState", "ThirdState"}, {"SecondState", "FourthState"}, {"ThirdState", "FourthState"}, {"FirstState", "SecondState", "ThirdState"}, {"FirstState", "ThirdState", "FourthState"}, {"SecondState", "ThirdState", "FourthState"}, {"FirstState","SecondState", "ThirdState", "FourthState"}},
374                                new int[] { 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1 });
375                
376                assertDDSearchableAttributeWildcardsWork(docType, principalId, "accountOpenDate",
377                                new String[][] {{"10/15/2009"}, {"10/15/2009","10/17/2009"}, {"10/14/2009","10/16/2009"}},
378                                new int[] { 1, 1, 0 });
379                
380                assertDDSearchableAttributeWildcardsWork(docType, principalId, "accountBalance",
381                                new String[][] {{"501.77"},{"501.77", "63.54"},{"501.78","501.74"}, {"502.00"}, {"0.00"} },
382                                new int[] { 1, 1, 0, 0, 0 });
383                
384                assertDDSearchableAttributeWildcardsWork(docType, principalId, "accountNumber",
385                                new String[][] {{"1234567890"},{"1234567890", "9876543210"},{"9876543210","77774"}, {"88881"}, {"0"} },
386                                new int[] { 1, 1, 0, 0, 0 });
387    }
388}