View Javadoc

1   /**
2    * Copyright 2005-2011 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.krad.workflow;
17  
18  import org.junit.Test;
19  import org.kuali.rice.core.api.uif.RemotableAttributeError;
20  import org.kuali.rice.core.api.uif.RemotableAttributeField;
21  import org.kuali.rice.core.api.util.type.KualiDecimal;
22  import org.kuali.rice.kew.api.KewApiConstants;
23  import org.kuali.rice.kew.api.document.search.DocumentSearchCriteria;
24  import org.kuali.rice.kew.api.document.search.DocumentSearchResults;
25  import org.kuali.rice.kew.api.exception.WorkflowException;
26  import org.kuali.rice.kew.docsearch.service.DocumentSearchService;
27  import org.kuali.rice.kew.doctype.bo.DocumentType;
28  import org.kuali.rice.kew.framework.document.attribute.SearchableAttribute;
29  import org.kuali.rice.kew.service.KEWServiceLocator;
30  import org.kuali.rice.kim.api.services.KimApiServiceLocator;
31  import org.kuali.rice.krad.UserSession;
32  import org.kuali.rice.krad.service.DocumentService;
33  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
34  import org.kuali.rice.krad.test.document.AccountWithDDAttributesDocument;
35  import org.kuali.rice.krad.util.GlobalVariables;
36  import org.kuali.rice.krad.workflow.attribute.DataDictionarySearchableAttribute;
37  import org.kuali.rice.test.BaselineTestCase;
38  import org.kuali.test.KRADTestCase;
39  
40  import java.sql.Date;
41  import java.sql.Timestamp;
42  import java.util.ArrayList;
43  import java.util.Calendar;
44  import java.util.Collections;
45  import java.util.HashMap;
46  import java.util.List;
47  import java.util.Map;
48  import java.util.concurrent.Callable;
49  
50  import static org.junit.Assert.*;
51  
52  /**
53   * This class performs various DataDictionarySearchableAttribute-related tests on the doc search, including verification of proper wildcard functionality. 
54   * 
55   * @author Kuali Rice Team (rice.collab@kuali.org)
56   */
57  //@BaselineTestCase.BaselineMode(BaselineTestCase.Mode.NONE)
58  public class DataDictionarySearchableAttributeTest extends KRADTestCase {
59  
60      @Override
61      public void setUp() throws Exception {
62          super.setUp();
63          GlobalVariables.setUserSession(new UserSession("quickstart"));
64      }
65      
66      private final static String ACCOUNT_WITH_DD_ATTRIBUTES_DOCUMENT_NAME = "AccountWithDDAttributes";
67      
68      enum DOCUMENT_FIXTURE {
69      	NORMAL_DOCUMENT("Testing NORMAL_DOCUMENT", new Integer(1234567890), "John Doe", new KualiDecimal(501.77), createDate(2009, Calendar.OCTOBER, 15), createTimestamp(2009, Calendar.NOVEMBER, 1, 0, 0, 0), "SecondState", true),
70      	ZERO_NUMBER_DOCUMENT("Testing ZERO_NUMBER_DOCUMENT", new Integer(0), "Jane Doe", new KualiDecimal(-100), createDate(2009, Calendar.OCTOBER, 16), createTimestamp(2015, Calendar.NOVEMBER, 2, 0, 0, 0), "FirstState", true),
71      	FALSE_AWAKE_DOCUMENT("Testing FALSE_AWAKE_DOCUMENT", new Integer(987654321), "John D'oh", new KualiDecimal(0.0), createDate(2006, Calendar.OCTOBER, 17), createTimestamp(1900, Calendar.NOVEMBER, 3, 0, 0, 0), "FourthState", false),
72      	ODD_NAME_DOCUMENT("Testing ODD_NAME_DOCUMENT", new Integer(88), "_", new KualiDecimal(10000051.0), createDate(2009, Calendar.OCTOBER, 18), createTimestamp(2009, Calendar.NOVEMBER, 4, 0, 0, 0), "FourthState", true),
73      	ODD_TIMESTAMP_DOCUMENT("Testing ODD_TIMESTAMP_DOCUMENT", new Integer(9000), "Shane Kloe", new KualiDecimal(4.54), createDate(2012, Calendar.OCTOBER, 19), createTimestamp(2007, Calendar.NOVEMBER, 5, 12, 4, 38), "ThirdState", false),
74      	ANOTHER_ODD_NAME_DOCUMENT("Testing ANOTHER_ODD_NAME_DOCUMENT", new Integer(1234567889), "---", new KualiDecimal(501), createDate(2009, Calendar.APRIL, 20), createTimestamp(2009, Calendar.NOVEMBER, 6, 12, 59, 59), "ThirdState", true),
75      	INVALID_STATE_DOCUMENT("Testing INVALID_STATE_DOCUMENT", new Integer(99999), "AAAAAAAAA", new KualiDecimal(2.22), createDate(2009, Calendar.OCTOBER, 21), createTimestamp(2009, Calendar.NOVEMBER, 7, 0, 0, 1), "SeventhState", true),
76      	WILDCARD_NAME_DOCUMENT("Testing WILDCARD_NAME_DOCUMENT", new Integer(1), "Sh*ne><K!=e?", new KualiDecimal(771.05), createDate(2054, Calendar.OCTOBER, 22), createTimestamp(2008, Calendar.NOVEMBER, 8, 12, 0, 0), "FirstState", true);
77      	
78      	private String accountDocumentDescription;
79      	private Integer accountNumber;
80      	private String accountOwner;
81      	private KualiDecimal accountBalance;
82      	private Date accountOpenDate;
83      	private Timestamp accountUpdateDateTime;
84      	private String accountState;
85      	private boolean accountAwake;
86      	
87      	private DOCUMENT_FIXTURE(String accountDocumentDescription, Integer accountNumber, String accountOwner, KualiDecimal accountBalance, Date accountOpenDate, Timestamp accountUpdateDateTime, String accountState, boolean accountAwake) {
88      		this.accountDocumentDescription = accountDocumentDescription;
89      		this.accountNumber = accountNumber;
90      		this.accountOwner = accountOwner;
91      		this.accountBalance = accountBalance;
92      		this.accountOpenDate = accountOpenDate;
93      		this.accountUpdateDateTime = accountUpdateDateTime;
94      		this.accountState = accountState;
95      		this.accountAwake = accountAwake;
96      	}
97      	
98      	public AccountWithDDAttributesDocument getDocument(DocumentService docService) throws WorkflowException {
99      		AccountWithDDAttributesDocument acctDoc = (AccountWithDDAttributesDocument) docService.getNewDocument(ACCOUNT_WITH_DD_ATTRIBUTES_DOCUMENT_NAME);
100     		acctDoc.getDocumentHeader().setDocumentDescription(this.accountDocumentDescription);
101     		acctDoc.setAccountNumber(this.accountNumber);
102     		acctDoc.setAccountOwner(this.accountOwner);
103     		acctDoc.setAccountBalance(this.accountBalance);
104     		acctDoc.setAccountOpenDate(this.accountOpenDate);
105     		acctDoc.setAccountUpdateDateTime(this.accountUpdateDateTime);
106     		acctDoc.setAccountState(this.accountState);
107     		acctDoc.setAccountAwake(this.accountAwake);
108     		
109     		return acctDoc;
110     	}
111     }
112 	
113 	/**
114 	 * Tests the use of multi-select and wildcard searches to ensure that they function correctly for DD searchable attributes on the doc search.
115 	 */
116     @Test
117 	public void testWildcardsAndMultiSelectsOnDDSearchableAttributes() throws Exception {
118 		DocumentService docService = KRADServiceLocatorWeb.getDocumentService();
119 		//docSearchService = KEWServiceLocator.getDocumentSearchService();
120 		DocumentType docType = KEWServiceLocator.getDocumentTypeService().findByName("AccountWithDDAttributes");
121         String principalName = "quickstart";
122         String principalId = KimApiServiceLocator.getPersonService().getPersonByPrincipalName(principalName).getPrincipalId();
123 		
124         // Route some test documents.
125 		docService.routeDocument(DOCUMENT_FIXTURE.NORMAL_DOCUMENT.getDocument(docService), "Routing NORMAL_DOCUMENT", null);
126 		docService.routeDocument(DOCUMENT_FIXTURE.ZERO_NUMBER_DOCUMENT.getDocument(docService), "Routing ZERO_NUMBER_DOCUMENT", null);
127 		docService.routeDocument(DOCUMENT_FIXTURE.FALSE_AWAKE_DOCUMENT.getDocument(docService), "Routing FALSE_AWAKE_DOCUMENT", null);
128 		docService.routeDocument(DOCUMENT_FIXTURE.ODD_NAME_DOCUMENT.getDocument(docService), "Routing ODD_NAME_DOCUMENT", null);
129 		docService.routeDocument(DOCUMENT_FIXTURE.ODD_TIMESTAMP_DOCUMENT.getDocument(docService), "Routing ODD_TIMESTAMP_DOCUMENT", null);
130 		docService.routeDocument(DOCUMENT_FIXTURE.ANOTHER_ODD_NAME_DOCUMENT.getDocument(docService), "Routing ANOTHER_ODD_NAME_DOCUMENT", null);
131 		docService.routeDocument(DOCUMENT_FIXTURE.INVALID_STATE_DOCUMENT.getDocument(docService), "Routing INVALID_STATE_DOCUMENT", null);
132 		docService.routeDocument(DOCUMENT_FIXTURE.WILDCARD_NAME_DOCUMENT.getDocument(docService), "Routing WILDCARD_NAME_DOCUMENT", null);
133 
134 		// Ensure that DD searchable attribute integer fields function correctly when searched on.
135 		// Note that negative numbers are disallowed by the NumericValidationPattern that validates this field.
136 		assertDDSearchableAttributeWildcardsWork(docType, principalId, "accountNumber",
137 				new String[] {"!1234567890", "*567*", "9???9", ">1", "987654321|1234567889", "<100", ">=99999", "<=-42", ">9000|<=1", "<1|>=1234567890",
138 						">1234567889&&<1234567890", ">=88&&<=99999", "0|>10&&<10000", "9000..1000000", "0..100|>1234567889", "1..10000&&>50", "250..50"},
139 				new int[]    {7            , -1     , -1     , 6   , 2                     , 3     , 4        , -1     , 6          , 2,
140 						0                         , 3              , 3              , 2              , 4                   , 2              , 0});
141 		
142 		// Ensure that DD searchable attribute string fields function correctly when searched on.
143 		// Note that DD searchable attributes cannot treat wildcards literally, so the "Sh*ne><K!=e" case below yields very different results.
144 		assertDDSearchableAttributeWildcardsWork(docType, principalId, "accountOwner",
145 				new String[] {"!John Doe", "!John*", "!John Doe&&!Shane Kloe", "!Jane ???", "!Jane Doe!John Doe", "_", "_|---", "Sh*ne><K!=e",
146 						">Jane Doe", "<Shane Kloe", ">=Johnny", "<=John D'oh", ">John Doe|<---", ">=AAAAAAAAA&&<=Jane Doe", ">---&&!John D'oh",
147 						"<Shane Kloe&&!John*", "*oe", "???? Doe", "Jane Doe..John Doe", "AAAAAAAAA..Shane Kloe&&!John Doe", "John D'oh|---..Jane Doe"},
148 				new int[]    {7          , 6       , 6                       , 7          , 6                   , 1  , 2      , 8,
149 						5          , 6            , 3         , 4            , 3               , 2                        , 6,
150 						4                    , 3    , 2         , 3                   , 5                                 , 4});
151 		
152 		// Ensure that DD searchable attribute float fields function correctly when searched on. Also ensure that the CurrencyFormatter is working.
153 		assertDDSearchableAttributeWildcardsWork(docType, principalId, "accountBalance",
154 				new String[] {"501.??", "*.54" , "!2.22", "10000051.0|771.05", "<0.0", ">501", "<=4.54", ">=-99.99", ">4.54|<=-1", ">=0&&<501.77", "!",
155 						"<=0|>=10000051", ">501&&<501.77", "-100|>771.05", "2.22..501", "-100..4.54&&<=0", "2.22|501.77..10000051.0", "Zero",
156 						"-$100", "<(501)&&>=($2.22)", "$4.54|<(1)", "($0.00)..$771.05", ">=$(500)", ")501(", "4.54$", "$501..0"},
157 				new int[]    {-1      , -1     , 7      , 2                  , 1     , 3     , 4       , 7         , 5           , 4             , -1,
158 						3               , 0              , 2             , 3          , 2                , 4                        , -1,
159 						1      , 2                  , 3           , 6                 , -1        , -1     , -1     , 0});
160 		
161 		// Ensure that DD searchable attribute date fields function correctly when searched on.
162 		// Note that dates with non-two-digit years outside the range of 1000 to 9999 will now fail validation.
163 		assertDDSearchableAttributeWildcardsWork(docType, principalId, "accountOpenDate",
164 				new String[] {"!10/15/2009", "Unknown", "10/15/2009|10/21/2009", "10/22/????", "*/*/05", ">10/17/06", "<=12-31-09&&>=10/16/2009",
165 						">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",
166 						"10/15/2009..10/21/2009", "1/1/2009..10/20/2009|10/22/2054", "<=06/32/03", ">2008&&<2011|10/17/06", 
167 						"<02/26/10500", ">05-07-333", ">=03/26/1001", "<=11-11-9900"},
168 				new int[]    {-1           , -1       , 2                      , -1          , -1      , 7          , 3,
169 						2                 , 4                         , 8                    , 1      , 3                      , -1        , -1,
170 						4                       , 5                                , -1          , 6                      ,
171 						-1            , -1          , 8             , 8});
172 		
173 		// Ensure that DD searchable attribute multi-select fields function correctly when searched on.
174 		// Currently, an exception is *not* thrown if the value given is not among the selectable values.
175 		assertDDSearchableAttributeWildcardsWork(docType, principalId, "accountStateMultiselect",
176 				new String[][] {{"FirstState"}, {"SecondState"}, {"ThirdState"}, {"FourthState"}, {"FirstState","ThirdState"},
177 						{"SecondState","FourthState"}, {"ThirdState","SecondState"}, {"FourthState","FirstState","SecondState"}, {"SeventhState"},
178 						{"ThirdState","FirstState","SecondState","FourthState"}},
179 				new int[]      {2             , 1              , 2             , 2              , 4,
180 						3                            , 3                           , 5                                         , 1,
181 						7});
182 		
183 		// Ensure that DD searchable attribute boolean fields function correctly when searched on.
184 		// TODO: Add the commented-out boolean search expressions back in once KULRICE-3698 is complete.
185 		assertDDSearchableAttributeWildcardsWork(docType, principalId, "accountAwake", new String[] {"Y", "N"}, new int[] {6, 2});
186 		/*assertDDSearchableAttributeWildcardsWork(docType, principalId, "accountAwake",
187 				new String[] {"Y", "N", "Z", "Neither", "n", "y", "true", "FALSE", "fAlSe", "TrUe", "NO", "Yes", "f", "F", "T", "t", "2", "0", "1",
188 						"Active", "INACTIVE", "On", "Off", "ON", "off", "EnAbLeD", "enabled", "dIsAbLeD", "DISABLED"},
189 				new int[]    {6  , 2  , -1 , -1       , 2  , 6  , 6     , 2      , 2      , 6     , 2   , 6    , 2  , 2  , 6  , 6  , -1 , 2  , 6  ,
190 						-1      , -1        , 6   , 2    , 6   , 2    , 6        , 6        , 2         , 2});*/
191 		
192 		// Ensure that DD searchable attribute timestamp fields function correctly when searched on.
193 		// Note that timestamps with non-two-digit years outside the range of 1000 to 9999 will now fail validation.
194 		assertDDSearchableAttributeWildcardsWork(docType, principalId, "accountUpdateDateTime",
195 				new String[] {"!11/01/2009 00:00:00", "11/05/07*", "11/02/2015 00:00:00|11/06/2009 12:59:59", "11/??/2009 ??:??:??", ">110609 12:59:59",
196 						"<=2009 1:2:3", ">=11/06/09 12:59:59", "<11/8/2008 12:00 PM", "Blank",
197 						"11/3/1900 00:00:00|>11-7-09 00:00:01", "02/29/2008 07:00:00..11/04/2009 00:00:00",
198 						"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",
199 						"<=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"},
200 				new int[]    {-1                    , -1         , 2                                        , -1                   , 2,
201 						3             , 3                    , 2                    , -1,
202 						2                                     , 3,
203 						4                                                        , -1          , 2,
204 						2                      , 8                      , -1                   , -1                     , 8});
205 	}
206     
207     /**
208      * Creates a date quickly
209      * 
210      * @param year the year of the date
211      * @param month the month of the date
212      * @param day the day of the date
213      * @return a new java.sql.Date initialized to the precise date given
214      */
215     private static Date createDate(int year, int month, int day) {
216     	Calendar date = Calendar.getInstance();
217 		date.set(year, month, day, 0, 0, 0);
218 		return new java.sql.Date(date.getTimeInMillis());
219     }
220     
221     /**
222      * Utility method to create a timestamp quickly
223      * 
224      * @param year the year of the timestamp
225      * @param month the month of the timestamp
226      * @param day the day of the timestamp
227      * @param hour the hour of the timestamp
228      * @param minute the minute of the timestamp
229      * @param second the second of the timestamp
230      * @return a new java.sql.Timestamp initialized to the precise time given
231      */
232     private static Timestamp createTimestamp(int year, int month, int day, int hour, int minute, int second) {
233     	Calendar date = Calendar.getInstance();
234     	date.set(year, month, day, hour, minute, second);
235     	return new java.sql.Timestamp(date.getTimeInMillis());
236     }
237 
238     /**
239      * A convenience method for testing wildcards on data dictionary searchable attributes.
240      *
241      * @param docType The document type containing the attributes.
242      * @param principalId The ID of the user performing the search.
243      * @param fieldName The name of the field on the test document.
244      * @param searchValues The search expressions to test. Has to be a String array (for regular fields) or a String[] array (for multi-select fields).
245      * @param resultSizes The number of expected documents to be returned by the search; use -1 to indicate that an error should have occurred.
246      * @throws Exception
247      */
248     private void assertDDSearchableAttributeWildcardsWork(DocumentType docType, String principalId, String fieldName, Object[] searchValues,
249     		int[] resultSizes) throws Exception {
250     	if (!(searchValues instanceof String[]) && !(searchValues instanceof String[][])) {
251     		throw new IllegalArgumentException("'searchValues' parameter has to be either a String[] or a String[][]");
252     	}
253     	DocumentSearchCriteria.Builder criteria = null;
254         DocumentSearchResults results = null;
255         DocumentSearchService docSearchService = KEWServiceLocator.getDocumentSearchService();
256         for (int i = 0; i < resultSizes.length; i++) {
257         	criteria = DocumentSearchCriteria.Builder.create();
258         	criteria.setDocumentTypeName(docType.getName());
259             if (searchValues instanceof String[][]) {
260                 String[] innerArray = (String[]) searchValues[i];
261                 for (int j=0; j<innerArray.length; j++) {
262                     criteria.addDocumentAttributeValue(fieldName, innerArray[j]);
263                 }
264             } else {
265                 criteria.addDocumentAttributeValue(fieldName, searchValues[i].toString());
266             }
267 
268         	try {
269         		results = docSearchService.lookupDocuments(principalId, criteria.build());
270         		if (resultSizes[i] < 0) {
271         			fail(fieldName + "'s search at loop index " + i + " should have thrown an exception");
272         		}
273         		if(resultSizes[i] != results.getSearchResults().size()){
274         			assertEquals(fieldName + "'s search results at loop index " + i + " returned the wrong number of documents.", resultSizes[i], results.getSearchResults().size());
275         		}
276         	} catch (Exception ex) {
277         		if (resultSizes[i] >= 0) {
278         			fail(fieldName + "'s search at loop index " + i + " should not have thrown an exception");
279         		}
280         	}
281         	GlobalVariables.clear();
282         }
283     }
284     
285     /**
286      * Validates that the search inputs does not cause a class cast exception
287      */
288     @Test
289     public void testValidateUserSearchInputsNoCast() throws Exception {
290     	DataDictionarySearchableAttribute searchableAttribute = new DataDictionarySearchableAttribute();
291     	final DocumentService documentService = KRADServiceLocatorWeb.getDocumentService();
292     	
293     	AccountWithDDAttributesDocument document = DOCUMENT_FIXTURE.NORMAL_DOCUMENT.getDocument(documentService);
294     	documentService.saveDocument(document);
295     	final String documentNumber = document.getDocumentNumber();
296     	    	
297     	Exception caughtException;
298     	List foundErrors;
299     	
300     	caughtException = null;
301     	foundErrors = new ArrayList();
302         DocumentSearchCriteria.Builder criteria = DocumentSearchCriteria.Builder.create();
303         criteria.setDocumentTypeName(ACCOUNT_WITH_DD_ATTRIBUTES_DOCUMENT_NAME);
304     	Map<String, List<String>> simpleParamMap = new HashMap<String, List<String>>();
305     	simpleParamMap.put("accountState", Collections.singletonList("FirstState"));
306         criteria.setDocumentAttributeValues(simpleParamMap);
307     	try {
308     		foundErrors = searchableAttribute.validateDocumentAttributeCriteria(null, criteria.build());
309     	} catch (RuntimeException re) {
310     		caughtException = re;
311     	}
312     	assertNull("Found Exception "+caughtException, caughtException);
313     	assertTrue("There were errors: "+foundErrors, (foundErrors == null || foundErrors.isEmpty()));
314     	
315     	caughtException = null;
316     	foundErrors = new ArrayList();
317     	Map<String, List<String>>  listParamMap = new HashMap<String, List<String>>();
318     	List<String> paramValues = new ArrayList<String>();
319     	paramValues.add("FirstState");
320     	paramValues.add("SecondState");
321     	listParamMap.put("accountState", paramValues);
322         criteria.setDocumentAttributeValues(listParamMap);
323     	try {
324     		foundErrors = searchableAttribute.validateDocumentAttributeCriteria(null, criteria.build());
325     	} catch (RuntimeException re) {
326     		caughtException = re;
327     	}
328     	assertNull("Found Exception "+caughtException, caughtException);
329     	assertTrue("There were errors: "+foundErrors, (foundErrors == null || foundErrors.isEmpty()));
330     }
331 
332     /**
333      * Tests handling resolution of error messages
334      */
335     @Test
336     public void testErrorMessageResolution() throws Exception {
337         final DataDictionarySearchableAttribute searchableAttribute = new DataDictionarySearchableAttribute();
338         final DocumentSearchCriteria.Builder criteria = DocumentSearchCriteria.Builder.create();
339         /*criteria.setDocumentTypeName(ACCOUNT_WITH_DD_ATTRIBUTES_DOCUMENT_NAME);
340         Map<String, List<String>> simpleParamMap = new HashMap<String, List<String>>();
341         simpleParamMap.put("accountState", Collections.singletonList("FirstState"));
342         criteria.setDocumentAttributeValues(simpleParamMap);*/
343         List<RemotableAttributeError> errors = GlobalVariables.doInNewGlobalVariables(new Callable<List<RemotableAttributeError>>() {
344             public List<RemotableAttributeError> call() {
345                 GlobalVariables.getMessageMap().putError("fake.property", "error.custom", "the error message");
346                 return searchableAttribute.validateDocumentAttributeCriteria(null, criteria.build());
347             }
348         });
349         assertEquals(1, errors.size());
350         assertEquals("the error message", errors.get(0).getMessage());
351     }
352     /**
353      * Test multiple value searches in the context of whole document search context
354      */
355     @Test
356     public void testMultiSelectIntegration() throws Exception {
357     	final DocumentService docService = KRADServiceLocatorWeb.getDocumentService();
358 		//docSearchService = KEWServiceLocator.getDocumentSearchService();
359 		DocumentType docType = KEWServiceLocator.getDocumentTypeService().findByName("AccountWithDDAttributes");
360         String principalName = "quickstart";
361         String principalId = KimApiServiceLocator.getPersonService().getPersonByPrincipalName(principalName).getPrincipalId();
362 		
363         // Route some test documents.
364 		docService.routeDocument(DOCUMENT_FIXTURE.NORMAL_DOCUMENT.getDocument(docService), "Routing NORMAL_DOCUMENT", null);
365 		
366 		assertDDSearchableAttributeWildcardsWork(docType, principalId, "accountStateMultiselect",
367 				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"}},
368 				new int[] { 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1 });
369 		
370 		assertDDSearchableAttributeWildcardsWork(docType, principalId, "accountOpenDate",
371 				new String[][] {{"10/15/2009"}, {"10/15/2009","10/17/2009"}, {"10/14/2009","10/16/2009"}},
372 				new int[] { 1, 1, 0 });
373 		
374 		assertDDSearchableAttributeWildcardsWork(docType, principalId, "accountBalance",
375 				new String[][] {{"501.77"},{"501.77", "63.54"},{"501.78","501.74"}, {"502.00"}, {"0.00"} },
376 				new int[] { 1, 1, 0, 0, 0 });
377 		
378 		assertDDSearchableAttributeWildcardsWork(docType, principalId, "accountNumber",
379 				new String[][] {{"1234567890"},{"1234567890", "9876543210"},{"9876543210","77774"}, {"88881"}, {"0"} },
380 				new int[] { 1, 1, 0, 0, 0 });
381     }
382 }