View Javadoc

1   /*
2    * Copyright 2006-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  
17  package org.kuali.rice.kew.bo.lookup;
18  
19  import org.apache.commons.beanutils.PropertyUtils;
20  import org.apache.commons.lang.StringUtils;
21  import org.kuali.rice.core.api.config.property.ConfigContext;
22  import org.kuali.rice.core.api.datetime.DateTimeService;
23  import org.kuali.rice.core.api.search.SearchOperator;
24  import org.kuali.rice.core.util.KeyValue;
25  import org.kuali.rice.core.util.RiceKeyConstants;
26  import org.kuali.rice.core.util.type.KualiDecimal;
27  import org.kuali.rice.core.util.type.KualiPercent;
28  import org.kuali.rice.core.web.format.BooleanFormatter;
29  import org.kuali.rice.core.web.format.CollectionFormatter;
30  import org.kuali.rice.core.web.format.DateFormatter;
31  import org.kuali.rice.core.web.format.Formatter;
32  import org.kuali.rice.core.web.format.TimestampAMPMFormatter;
33  import org.kuali.rice.kew.docsearch.DocSearchCriteriaDTO;
34  import org.kuali.rice.kew.docsearch.DocumentLookupCriteriaBuilder;
35  import org.kuali.rice.kew.docsearch.DocumentLookupCriteriaProcessor;
36  import org.kuali.rice.kew.docsearch.DocumentLookupCriteriaProcessorKEWAdapter;
37  import org.kuali.rice.kew.docsearch.DocumentRouteHeaderEBO;
38  import org.kuali.rice.kew.docsearch.DocumentSearchGenerator;
39  import org.kuali.rice.kew.docsearch.DocumentSearchResult;
40  import org.kuali.rice.kew.docsearch.DocumentSearchResultComponents;
41  import org.kuali.rice.kew.docsearch.SavedSearchResult;
42  import org.kuali.rice.kew.docsearch.SearchAttributeCriteriaComponent;
43  import org.kuali.rice.kew.docsearch.StandardDocumentSearchCriteriaProcessor;
44  import org.kuali.rice.kew.docsearch.service.DocumentSearchService;
45  import org.kuali.rice.kew.doctype.bo.DocumentType;
46  import org.kuali.rice.kew.exception.WorkflowServiceError;
47  import org.kuali.rice.kew.exception.WorkflowServiceErrorException;
48  import org.kuali.rice.kew.lookup.valuefinder.SavedSearchValuesFinder;
49  import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue;
50  import org.kuali.rice.kew.service.KEWServiceLocator;
51  import org.kuali.rice.kew.util.KEWConstants;
52  import org.kuali.rice.kew.util.KEWPropertyConstants;
53  import org.kuali.rice.kew.web.KeyValueSort;
54  import org.kuali.rice.kim.bo.Person;
55  import org.kuali.rice.kns.document.authorization.BusinessObjectRestrictions;
56  import org.kuali.rice.kns.lookup.HtmlData;
57  import org.kuali.rice.kns.lookup.KualiLookupableHelperServiceImpl;
58  import org.kuali.rice.kns.util.FieldUtils;
59  import org.kuali.rice.kns.web.comparator.CellComparatorHelper;
60  import org.kuali.rice.kns.web.struts.form.LookupForm;
61  import org.kuali.rice.kns.web.ui.Column;
62  import org.kuali.rice.kns.web.ui.Field;
63  import org.kuali.rice.kns.web.ui.ResultRow;
64  import org.kuali.rice.kns.web.ui.Row;
65  import org.kuali.rice.krad.bo.BusinessObject;
66  import org.kuali.rice.kns.datadictionary.BusinessObjectEntry;
67  import org.kuali.rice.krad.exception.ValidationException;
68  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
69  import org.kuali.rice.krad.util.GlobalVariables;
70  import org.kuali.rice.krad.util.KRADConstants;
71  import org.kuali.rice.krad.util.MessageMap;
72  import org.kuali.rice.krad.util.ObjectUtils;
73  import org.kuali.rice.krad.util.UrlFactory;
74  
75  import java.lang.reflect.InvocationTargetException;
76  import java.math.BigDecimal;
77  import java.sql.Date;
78  import java.sql.Timestamp;
79  import java.util.ArrayList;
80  import java.util.Collection;
81  import java.util.HashMap;
82  import java.util.List;
83  import java.util.Map;
84  import java.util.Properties;
85  import java.util.regex.Matcher;
86  import java.util.regex.Pattern;
87  
88  /**
89   * Lookupable helper class for new doc search
90   * @author Kuali Rice Team (rice.collab@kuali.org)
91   *
92   */
93  public class DocSearchCriteriaDTOLookupableHelperServiceImpl extends KualiLookupableHelperServiceImpl {
94  
95  	private static final long serialVersionUID = -5162419674659967408L;
96  	DateTimeService dateTimeService;
97  	DocumentLookupCriteriaProcessor processor;
98  	boolean savedSearch = false;
99  	private static final Pattern HREF_PATTERN = Pattern.compile("<a href=\"([^\"]+)\"");
100 
101 	/**
102 	 * @see org.kuali.rice.kew.bo.lookup.DocumentRouteHeaderValueLookupableHelperService#setDateTimeService(org.kuali.rice.krad.service.DateTimeService)
103 	 */
104 	public void setDateTimeService(DateTimeService dateTimeService) {
105 		this.dateTimeService = dateTimeService;
106 	}
107 
108 	/**
109 	 * @see org.kuali.rice.krad.lookup.AbstractLookupableHelperServiceImpl#performLookup(org.kuali.rice.krad.web.struts.form.LookupForm, java.util.Collection, boolean)
110 	 */
111 	@Override
112 	public Collection performLookup(LookupForm lookupForm,
113 			Collection resultTable, boolean bounded) {
114 
115 		//TODO: ideally implement KNS updates to make this not require code from the parent
116 
117     	Map<String,String[]> parameters = this.getParameters();
118 
119     	DocSearchCriteriaDTO criteria = null;
120     	if(savedSearch) {
121     		//TODO: set the criteria on this from below method instead of this (so we're not calling out twice for the same object)
122     		DocumentSearchService docSearchService = KEWServiceLocator.getDocumentSearchService();
123 
124     		String savedSearchName = ((String[])getParameters().get("savedSearchName"))[0];
125     		SavedSearchResult savedSearchResult = null;
126     		if(StringUtils.isNotEmpty(savedSearchName)) {
127     			savedSearchResult = docSearchService.getSavedSearchResults(GlobalVariables.getUserSession().getPrincipalId(), savedSearchName);
128     		}
129     		if(savedSearchResult!=null){
130     			criteria = savedSearchResult.getDocSearchCriteriaDTO();
131     		}
132     		savedSearch=false;
133     	} else {
134     		Map<String,String[]> fixedParameters = new HashMap<String,String[]>();
135     		Map<String,String> changedDateFields = preprocessDateFields(lookupForm.getFieldsForLookup());
136     		fixedParameters.putAll(this.getParameters());
137     		for (Map.Entry<String,String> prop : changedDateFields.entrySet()) {
138 				fixedParameters.remove(prop.getKey());
139     			fixedParameters.put(prop.getKey(), new String[]{prop.getValue()});
140 			}
141     		criteria = DocumentLookupCriteriaBuilder.populateCriteria(fixedParameters);
142 
143     	}
144 
145     	Collection<DocumentSearchResult> displayList=null;
146 
147     	DocumentSearchResultComponents components = null;
148     	try {
149     		components = KEWServiceLocator.getDocumentSearchService().getList(GlobalVariables.getUserSession().getPrincipalId(), criteria);
150     	} catch (WorkflowServiceErrorException wsee) {
151     		for (WorkflowServiceError workflowServiceError : (List<WorkflowServiceError>)wsee.getServiceErrors()) {
152 				if(workflowServiceError.getMessageMap() != null && workflowServiceError.getMessageMap().hasErrors()){
153 					// merge the message maps
154 					GlobalVariables.getMessageMap().merge(workflowServiceError.getMessageMap());
155 				}else{
156 					//TODO: can we add something to this to get it to highlight the right field too?  Maybe in arg1
157 					GlobalVariables.getMessageMap().putError(workflowServiceError.getMessage(), RiceKeyConstants.ERROR_CUSTOM, workflowServiceError.getMessage());
158 				}
159     		};
160     	}
161 
162     	if(!GlobalVariables.getMessageMap().hasNoErrors()) {
163         	throw new ValidationException("error with doc search");
164         }
165 
166     	// check various warning conditions
167 
168     	if (criteria.isOverThreshold() && criteria.getSecurityFilteredRows() > 0) {
169     	    GlobalVariables.getMessageMap().putWarning(KRADConstants.GLOBAL_MESSAGES, "docsearch.DocumentSearchService.exceededThresholdAndSecurityFiltered", String.valueOf(components.getSearchResults().size()), String.valueOf(criteria.getSecurityFilteredRows()));
170     	} else if (criteria.getSecurityFilteredRows() > 0) {
171     	    GlobalVariables.getMessageMap().putWarning(KRADConstants.GLOBAL_MESSAGES, "docsearch.DocumentSearchService.securityFiltered", String.valueOf(criteria.getSecurityFilteredRows()));
172     	} else if (criteria.isOverThreshold()) {
173     		GlobalVariables.getMessageMap().putWarning(KRADConstants.GLOBAL_MESSAGES,"docsearch.DocumentSearchService.exceededThreshold", String.valueOf(components.getSearchResults().size()));
174     	}
175 
176     	for (Row row : this.getRows()) {
177 			for (Field field : row.getFields()) {
178 				if(StringUtils.equals(field.getPropertyName(),"fromDateCreated") && StringUtils.isEmpty(field.getPropertyValue())) {
179 					field.setPropertyValue(criteria.getFromDateCreated());
180 				}
181 			}
182 		}
183 
184     	List<DocumentSearchResult> result = components.getSearchResults();
185 //    	for (DocumentSearchResult documentSearchResult : result) {
186 			displayList = result;//.getResultContainers();
187 //		}
188 
189 		//####BEGIN COPIED CODE#########
190         setBackLocation((String) lookupForm.getFieldsForLookup().get(KRADConstants.BACK_LOCATION));
191         setDocFormKey((String) lookupForm.getFieldsForLookup().get(KRADConstants.DOC_FORM_KEY));
192 
193 //###COMENTED OUT
194 //		  Collection displayList;
195 //        // call search method to get results
196 //        if (bounded) {
197 //            displayList = getSearchResults(lookupForm.getFieldsForLookup());
198 //        }
199 //        else {
200 //            displayList = getSearchResultsUnbounded(lookupForm.getFieldsForLookup());
201 //        }
202 //##COMENTED OUT
203 
204         HashMap<String,Class> propertyTypes = new HashMap<String, Class>();
205 
206         boolean hasReturnableRow = false;
207 
208         List returnKeys = getReturnKeys();
209         List pkNames = getBusinessObjectMetaDataService().listPrimaryKeyFieldNames(getBusinessObjectClass());
210         Person user = GlobalVariables.getUserSession().getPerson();
211 
212         // iterate through result list and wrap rows with return url and action urls
213 
214 //COMMENTING THIS OUT FOR NOW
215 //        for (Iterator iter = displayList.iterator(); iter.hasNext();) {
216 //            BusinessObject element = (BusinessObject) iter.next();
217 //        	if(element instanceof PersistableBusinessObject){
218 //                lookupForm.setLookupObjectId(((PersistableBusinessObject)element).getObjectId());
219 //            }
220         DocumentRouteHeaderEBO element = new DocSearchCriteriaDTO();
221         //TODO: additional BORestrictions through generator or component to lock down per document?
222     	BusinessObjectRestrictions businessObjectRestrictions = getBusinessObjectAuthorizationService().getLookupResultRestrictions(element, user);
223 
224 //          String actionUrls = getActionUrls(element, pkNames, businessObjectRestrictions);
225 //ADDED (4 lines)
226         for (DocumentSearchResult documentSearchResult : result) {
227 
228 
229 
230 
231 
232         	DocumentSearchResult docSearchResult = (DocumentSearchResult)documentSearchResult;
233 //TODO: where to get these from?
234 //        	HtmlData returnUrl = new AnchorHtmlData();
235         	String actionUrls = "";
236 
237 //ADDED (3)
238             List<? extends Column> origColumns = components.getColumns();//getColumns();
239             List<Column> newColumns = new ArrayList<Column>();
240             List<KeyValueSort> keyValues = docSearchResult.getResultContainers();
241             for (int i = 0; i < origColumns.size(); i++) {
242 
243 //            for (Iterator iterator = columns.iterator(); iterator.hasNext();) {
244 
245 //                Column col = (Column) iterator.next();
246 //ADDED 3
247             	  Column col = (Column) origColumns.get(i);
248             	  KeyValueSort keyValue = null;
249             	  for (KeyValueSort keyValueFromList : keyValues) {
250             		  if(StringUtils.equals(col.getPropertyName(), keyValueFromList.getKey())) {
251             			  keyValue = keyValueFromList;
252             			  break;
253             		  }
254             	  }
255             	  if(keyValue==null) {
256             		  //means we didn't find an indexed value for this, this seems bad but happens a lot we should research why
257             		  keyValue = new KeyValueSort();
258 //            		  System.out.println("column: "+col.getPropertyName()+"has an empty KeyValue, this should never happen");
259             	  }
260 
261             	  //Set values from keyvalue on column
262             	  col.setPropertyValue(keyValue.getUserDisplayValue());
263 
264             	  String propertyName = col.getPropertyName();
265 				if(StringUtils.isEmpty(col.getColumnTitle())) {
266             		  String labelMessageKey;
267             		  if(StringUtils.equals(propertyName,KEWPropertyConstants.DOC_SEARCH_RESULT_PROPERTY_NAME_ROUTE_LOG)) {
268             			  //TODO: find a better place for this
269             			  labelMessageKey = "Route Log";
270             		  } else {
271             			  //TODO: change this to an enum (or another dd property)
272             			  propertyName=(StringUtils.equals(propertyName,"docTypeLabel"))?"docTypeFullName":propertyName;
273             			  propertyName=(StringUtils.equals(propertyName,"docRouteStatusCodeDesc"))?"docRouteStatus":propertyName;
274             			  propertyName=(StringUtils.equals(propertyName,"documentTitle"))?"docTitle":propertyName;
275             			  labelMessageKey = getDataDictionaryService().getAttributeLabel(DocSearchCriteriaDTO.class,propertyName);
276             		  }
277             		  col.setColumnTitle(labelMessageKey);
278             	  }
279 
280 				if(StringUtils.equals(propertyName, KEWPropertyConstants.DOC_SEARCH_RESULT_PROPERTY_NAME_DOCUMENT_ID)) {
281 					((DocSearchCriteriaDTO)element).setDocumentId(col.getPropertyValue());
282 				}
283 
284             	Formatter formatter = col.getFormatter();
285 
286                 // pick off result column from result list, do formatting
287                 String propValue = KRADConstants.EMPTY_STRING;
288 //                Object prop = ObjectUtils.getPropertyValue(element, col.getPropertyName());
289 //ADDED
290                 Object prop=keyValue.getSortValue();
291 
292                 // set comparator and formatter based on property type
293                 Class propClass = propertyTypes.get(propertyName);
294                 if ( propClass == null ) {
295                     try {
296                     	//ADDED 3
297                     	if(prop!=null) {
298                     		propertyTypes.put(propertyName, prop.getClass());
299                     		propClass = prop.getClass();
300                     	}
301 
302                     	//propClass = ObjectUtils.getPropertyType( element, col.getPropertyName(), getPersistenceStructureService() );
303 //                    	propertyTypes.put( col.getPropertyName(), propClass );
304                     } catch (Exception e) {
305 //                        throw new RuntimeException("Cannot access PropertyType for property " + "'" + col.getPropertyName() + "' " + " on an instance of '" + element.getClass().getName() + "'.", e);
306                     }
307                 }
308 
309 
310                 //TODO: check exisiting formatter here, ideally we should be getting this formatter from col.getFormatter in most cases
311                 // formatters
312                 if (prop != null) {
313                     if (formatter == null) {
314                         // for Booleans, always use BooleanFormatter
315                         if (prop instanceof Boolean) {
316                             formatter = new BooleanFormatter();
317                         }
318 
319                         // for Dates, always use DateFormatter
320                         if (prop instanceof Date) {
321                             formatter = new DateFormatter();
322                         }
323                         //#ADDED 3
324                         if (prop instanceof Timestamp) {
325                             formatter = new TimestampAMPMFormatter();
326                         }
327 
328                         // for collection, use the list formatter if a formatter hasn't been defined yet
329                         if (prop instanceof Collection && formatter == null) {
330                             formatter = new CollectionFormatter();
331                         }
332                     }
333                     if (formatter != null) {
334                         //hack for Currency values as big decimal
335                         if (prop instanceof BigDecimal  && formatter.getImplementationClass().equals("org.kuali.rice.krad.web.format.CurrencyFormatter")) {
336                             prop = new KualiDecimal((BigDecimal)prop);
337                         } else if (prop instanceof BigDecimal  && formatter.getImplementationClass().equals("org.kuali.rice.krad.web.format.PercentageFormatter")) {
338                             prop = new KualiPercent((BigDecimal)prop);
339                         }
340                          propValue = (String) formatter.format(prop);
341                     }
342                     else {
343                         propValue = prop.toString();
344                     }
345                 }
346 
347                 // comparator
348                 col.setComparator(CellComparatorHelper.getAppropriateComparatorForPropertyClass(propClass));
349                 col.setValueComparator(CellComparatorHelper.getAppropriateValueComparatorForPropertyClass(propClass));
350 
351                 //TODO: can we call into a method in the result processor to get this (or set something on the criteria)
352 //                propValue = maskValueIfNecessary(element.getClass(), col.getPropertyName(), propValue, businessObjectRestrictions);
353 
354                 col.setPropertyValue(propValue);
355 
356                 if (StringUtils.isNotBlank(propValue)) {
357 //                    col.setColumnAnchor(getInquiryUrl(element, col.getPropertyName()));
358 //ADDED (3 lines)
359                 	HtmlData.AnchorHtmlData anchor = new HtmlData.AnchorHtmlData(KRADConstants.EMPTY_STRING, KRADConstants.EMPTY_STRING);
360                 	//TODO: change to grab URL from config variable
361                 	if(StringUtils.isNotEmpty(keyValue.getValue()) && StringUtils.equals("documentId", keyValue.getKey())) {
362                 	    String target = StringUtils.substringBetween(keyValue.getValue(), "target=\"", "\"");
363                 	    if (target == null) {
364                 	        target = "_self";
365                 	    }
366                 	    anchor.setTarget(target.trim());
367                 		if(!DocSearchCriteriaDTO.SUPER_USER_SEARCH_INDICATOR_STRING.equals(criteria.getSuperUserSearch())) {
368                 			anchor.setHref(".."+KEWConstants.WEBAPP_DIRECTORY+"/"+StringUtils.substringBetween(keyValue.getValue(), "<a href=\"", "docId=")+"docId="+keyValue.getUserDisplayValue());
369                 		} else {
370                 			// KULRICE-3035: Append the doc search's returnLocation parameter to the superuser page URL.
371     						String returnLoc = "";
372     						if (this.getParameters().containsKey(KRADConstants.RETURN_LOCATION_PARAMETER)) {
373     							returnLoc = (new StringBuilder()).append("&").append(KRADConstants.RETURN_LOCATION_PARAMETER).append("=").append(
374     									((String[])this.getParameters().get(KRADConstants.RETURN_LOCATION_PARAMETER))[0]).toString();
375     						}
376     						else if (StringUtils.isNotBlank(this.getBackLocation())) {
377     							returnLoc = (new StringBuilder()).append("&").append(KRADConstants.RETURN_LOCATION_PARAMETER).append("=").append(
378     									this.getBackLocation()).toString();
379     						}
380 
381                 			anchor.setHref(".."+KEWConstants.WEBAPP_DIRECTORY+"/"+StringUtils.substringBetween(keyValue.getValue(), "<a href=\"", "documentId=")+"documentId="+keyValue.getUserDisplayValue() + returnLoc);
382                 		}
383                         col.setMaxLength(100); //for now force this
384                 	} else if(StringUtils.isNotEmpty(keyValue.getValue()) && StringUtils.equals(KEWPropertyConstants.DOC_SEARCH_RESULT_PROPERTY_NAME_ROUTE_LOG, keyValue.getKey())) {
385                 		Matcher hrefMatcher = HREF_PATTERN.matcher(keyValue.getValue());
386                 		String matchedURL = "";
387                 		if (hrefMatcher.find()) {
388                 			matchedURL = hrefMatcher.group(1);
389                 		}
390                 		anchor.setHref(".."+KEWConstants.WEBAPP_DIRECTORY+"/"+matchedURL);
391                 		String target = StringUtils.substringBetween(keyValue.getValue(), "target=\"", "\"");
392                         if (target == null) {
393                             target = "_self";
394                         }
395                         anchor.setTarget(target.trim());
396                 		col.setMaxLength(100); //for now force this
397                         keyValue.setValue(keyValue.getUserDisplayValue());
398                         col.setEscapeXMLValue(false);
399                 	} else if (StringUtils.isNotEmpty(keyValue.getValue()) && StringUtils.equals(KEWPropertyConstants.DOC_SEARCH_RESULT_PROPERTY_NAME_INITIATOR, keyValue.getKey())) {
400                 		anchor.setHref(StringUtils.substringBetween(keyValue.getValue(), "<a href=\"", "\" target=\"_blank\""));
401                 		col.setMaxLength(100); //for now force this
402                 	}
403 
404                 	col.setColumnAnchor(anchor);
405 
406                 }
407                 Column newCol = (Column)ObjectUtils.deepCopy(col);
408                 newColumns.add(newCol);
409 
410             }
411 
412             HtmlData returnUrl = getReturnUrl(element, lookupForm, returnKeys, businessObjectRestrictions);
413             ResultRow row = new ResultRow(newColumns, returnUrl.constructCompleteHtmlTag(), actionUrls);
414             row.setRowId(returnUrl.getName());
415             // because of concerns of the BO being cached in session on the ResultRow,
416             // let's only attach it when needed (currently in the case of export)
417             if (getBusinessObjectDictionaryService().isExportable(getBusinessObjectClass())) {
418             	//            	row.setBusinessObject(element);
419             }
420 
421             //            boolean rowReturnable = isResultReturnable(element);
422             //ADDED
423             boolean rowReturnable = true;
424             row.setRowReturnable(rowReturnable);
425             if (rowReturnable) {
426             	hasReturnableRow = true;
427             }
428             resultTable.add(row);
429         }
430 
431 
432         lookupForm.setHasReturnableRow(hasReturnableRow);
433 
434         return displayList;
435 		//####END COPIED CODE#########
436 	}
437 
438 
439 
440 
441 
442 	/**
443 	 * @see org.kuali.rice.krad.lookup.AbstractLookupableHelperServiceImpl#getInquiryUrl(org.kuali.rice.krad.bo.BusinessObject, java.lang.String)
444 	 */
445 	@Override
446 	public HtmlData getInquiryUrl(BusinessObject bo, String propertyName) {
447 		//FIXME: ctk - make sure and check that it's ok to do this here.  I may move this out to the doc search processor
448 		if(KEWPropertyConstants.DOC_SEARCH_RESULT_PROPERTY_NAME_DOCUMENT_ID.equals(propertyName)) {
449 
450 			HtmlData.AnchorHtmlData link = new HtmlData.AnchorHtmlData();
451 			DocumentRouteHeaderValue doc = (DocumentRouteHeaderValue)bo;
452 			//if !superuser
453 			String documentId = doc.getDocumentId();
454 			link.setDisplayText(documentId+"");
455 
456 			String href = ConfigContext.getCurrentContextConfig().getKRBaseURL()+"/"+ KEWConstants.APP_CODE + "/" +
457 			KEWConstants.DOC_HANDLER_REDIRECT_PAGE + "?" + KEWConstants.COMMAND_PARAMETER + "=" +
458 			KEWConstants.DOCSEARCH_COMMAND + "&" + KEWConstants.DOCUMENT_ID_PARAMETER + "=" + documentId;
459 
460 			link.setHref(href);
461 
462 			return link;
463 		}
464 
465 		return super.getInquiryUrl(bo, propertyName);
466 	}
467 
468 
469 	/**
470 	 * @see org.kuali.rice.krad.lookup.AbstractLookupableHelperServiceImpl#setRows()
471 	 */
472 	@Override
473 	protected void setRows() {
474 	    this.setRows(new HashMap<String,String[]>(), null);
475 	}
476 
477 
478 	/**
479 	 * @see org.kuali.rice.krad.lookup.AbstractLookupableHelperServiceImpl#setRows()
480 	 */
481 	protected void setRows(Map fieldValues, String docTypeName) {
482 		// TODO chris - this method should call the criteria processor adapter which will
483 		//call the criteria processor (either standard or custom) and massage the data into the proper format
484 		//this is called by setbo in super(which is called by form) so should be called when the page needs refreshing
485 
486 		//TODO: move over code that checks for doctype (actually should that be in the refresh, since that's where the doc type will be coming back to?)
487 
488 
489 		//###START LOOKUP ROW CODE Not sure if we need these but they may be valuable for eventually forcing all standard field customization in the xml
490 		if (getRows() == null) {
491 		    super.setRows();
492 		}
493 		List<Row> lookupRows = new ArrayList<Row>();
494 		//copy the current rows
495 		for (Row row : getRows()) {
496 			lookupRows.add(row);
497 		}
498 		//clear out
499 		getRows().clear();
500 
501         processor = new DocumentLookupCriteriaProcessorKEWAdapter();
502 
503 
504 		DocumentType docType = null;
505 
506 		if(StringUtils.isNotEmpty(docTypeName)) {
507 			docType = getValidDocumentType((String)docTypeName);
508 		}
509 
510 		DocumentLookupCriteriaProcessorKEWAdapter documentLookupCriteriaProcessorKEWAdapter = (DocumentLookupCriteriaProcessorKEWAdapter)processor;
511 		if(processor != null && documentLookupCriteriaProcessorKEWAdapter.getCriteriaProcessor()!=null) {
512 			if(docType==null) {
513 				documentLookupCriteriaProcessorKEWAdapter.setCriteriaProcessor(new StandardDocumentSearchCriteriaProcessor());
514 			} else if(!StringUtils.equals(docTypeName, documentLookupCriteriaProcessorKEWAdapter.getCriteriaProcessor().getDocSearchCriteriaDTO().getDocTypeFullName())){
515 				documentLookupCriteriaProcessorKEWAdapter.setCriteriaProcessor(docType.getDocumentSearchCriteriaProcessor());
516 			}
517 		} else {
518 			if(docType==null) {
519 				documentLookupCriteriaProcessorKEWAdapter.setCriteriaProcessor(new StandardDocumentSearchCriteriaProcessor());
520 			} else {
521 				documentLookupCriteriaProcessorKEWAdapter.setCriteriaProcessor(docType.getDocumentSearchCriteriaProcessor());
522 			}
523 		}
524 		//TODO: This should probably be moved into spring injection since it's a constant
525 		documentLookupCriteriaProcessorKEWAdapter.setDataDictionaryService(getDataDictionaryService());
526 
527 		boolean detailed=false;
528 		if(this.getParameters().containsKey("isAdvancedSearch")) {
529 			detailed = DocSearchCriteriaDTO.ADVANCED_SEARCH_INDICATOR_STRING.equalsIgnoreCase(((String[])this.getParameters().get("isAdvancedSearch"))[0]);
530 		} else if(fieldValues.containsKey("isAdvancedSearch")) {
531 			detailed = DocSearchCriteriaDTO.ADVANCED_SEARCH_INDICATOR_STRING.equalsIgnoreCase((String) fieldValues.get("isAdvancedSearch"));
532 		}
533 
534 		boolean superSearch=false;
535 		if(this.getParameters().containsKey(("superUserSearch"))) {
536 			superSearch = DocSearchCriteriaDTO.SUPER_USER_SEARCH_INDICATOR_STRING.equalsIgnoreCase(((String[])this.getParameters().get("superUserSearch"))[0]);
537 		} else if(fieldValues.containsKey("superUserSearch")) {
538 			superSearch = DocSearchCriteriaDTO.SUPER_USER_SEARCH_INDICATOR_STRING.equalsIgnoreCase((String)fieldValues.get("superUserSearch"));
539 		}
540 
541 		//call get rows
542 		List<Row> rows = processor.getRows(docType,lookupRows, detailed, superSearch);
543 
544 		BusinessObjectEntry boe = (BusinessObjectEntry) KRADServiceLocatorWeb.getDataDictionaryService().getDataDictionary().getBusinessObjectEntry(this.getBusinessObjectClass().getName());
545         int numCols = boe.getLookupDefinition().getNumOfColumns();
546         if(numCols == 0) {
547 			numCols = KRADConstants.DEFAULT_NUM_OF_COLUMNS;
548 		}
549 
550 		super.getRows().addAll(FieldUtils.wrapFields(this.getFields(rows), numCols));
551 
552 	}
553 
554 	 private List<Field> getFields(List<Row> rows){
555 	    	List<Field> rList = new ArrayList<Field>();
556 
557 	    	for(Row r: rows){
558 	    		for(Field f: r.getFields()){
559 	    			rList.add(f);
560 	    		}
561 	    	}
562 
563 	    	return rList;
564 	    }
565 
566 	   private void setRowsAfterClear(DocSearchCriteriaDTO searchCriteria, Map<String,String[]> fieldValues) {
567 	        // TODO chris - this method should call the criteria processor adapter which will
568 	        //call the criteria processor (either standard or custom) and massage the data into the proper format
569 	        //this is called by setbo in super(which is called by form) so should be called when the page needs refreshing
570 
571 	        //TODO: move over code that checks for doctype (actually should that be in the refresh, since that's where the doc type will be coming back to?)
572 	       if (getRows() == null) {
573 	            super.setRows();
574 	        }
575 	       List<Row> lookupRows = new ArrayList<Row>();
576 	        //copy the current rows
577 	        for (Row row : getRows()) {
578 	            lookupRows.add(row);
579 	        }
580 	        super.getRows().clear();
581 
582 	        processor = new DocumentLookupCriteriaProcessorKEWAdapter();
583 
584 	        String docTypeName = searchCriteria.getDocTypeFullName();
585 	        DocumentType docType = null;
586 
587 	        if(StringUtils.isNotEmpty(docTypeName)) {
588 	            docType = getValidDocumentType(docTypeName);
589 	        }
590 
591 	        DocumentLookupCriteriaProcessorKEWAdapter documentLookupCriteriaProcessorKEWAdapter = (DocumentLookupCriteriaProcessorKEWAdapter)processor;
592 	        if(processor != null && documentLookupCriteriaProcessorKEWAdapter.getCriteriaProcessor()!=null) {
593 	            if(docType==null) {
594 	                documentLookupCriteriaProcessorKEWAdapter.setCriteriaProcessor(new StandardDocumentSearchCriteriaProcessor());
595 	            } else if(!StringUtils.equals(docTypeName, documentLookupCriteriaProcessorKEWAdapter.getCriteriaProcessor().getDocSearchCriteriaDTO().getDocTypeFullName())){
596 	                documentLookupCriteriaProcessorKEWAdapter.setCriteriaProcessor(docType.getDocumentSearchCriteriaProcessor());
597 	            }
598 	        } else {
599 	            if(docType==null) {
600 	                documentLookupCriteriaProcessorKEWAdapter.setCriteriaProcessor(new StandardDocumentSearchCriteriaProcessor());
601 	            } else {
602 	                documentLookupCriteriaProcessorKEWAdapter.setCriteriaProcessor(docType.getDocumentSearchCriteriaProcessor());
603 	            }
604 	        }
605 	        //TODO: This should probably be moved into spring injection since it's a constant
606 	        documentLookupCriteriaProcessorKEWAdapter.setDataDictionaryService(getDataDictionaryService());
607 
608 	        boolean detailed=false;
609 	        if(this.getParameters().containsKey("isAdvancedSearch")) {
610 	            detailed = DocSearchCriteriaDTO.ADVANCED_SEARCH_INDICATOR_STRING.equalsIgnoreCase(((String[])this.getParameters().get("isAdvancedSearch"))[0]);
611 	        } else if(fieldValues.containsKey("isAdvancedSearch")) {
612 	            detailed = DocSearchCriteriaDTO.ADVANCED_SEARCH_INDICATOR_STRING.equalsIgnoreCase((String) fieldValues.get("isAdvancedSearch")[0]);
613 	        }
614 
615 	        boolean superSearch=false;
616 	        if(this.getParameters().containsKey(("superUserSearch"))) {
617 	            superSearch = DocSearchCriteriaDTO.SUPER_USER_SEARCH_INDICATOR_STRING.equalsIgnoreCase(((String[])this.getParameters().get("superUserSearch"))[0]);
618 	        } else if(fieldValues.containsKey("superUserSearch")) {
619 	            superSearch = DocSearchCriteriaDTO.SUPER_USER_SEARCH_INDICATOR_STRING.equalsIgnoreCase((String)fieldValues.get("superUserSearch")[0]);
620 	        }
621 	        //call get rows
622 	        List<Row> rows = documentLookupCriteriaProcessorKEWAdapter.getRows(docType, super.getRows(), detailed, superSearch);
623 
624 	        super.getRows().addAll(rows);
625 
626 	        //Set field values from DocSearchCriteria
627 	        if(StringUtils.isNotEmpty(docTypeName)) {
628     	        for (Row row : super.getRows()) {
629     	            for (Field field : row.getFields()) {
630     	                //if from date, strip off prefix
631     	                String propertyName = null;
632     	                if(field.getPropertyName().startsWith(KRADConstants.LOOKUP_RANGE_LOWER_BOUND_PROPERTY_PREFIX)) {
633     	                    propertyName = StringUtils.remove(field.getPropertyName(), KRADConstants.LOOKUP_RANGE_LOWER_BOUND_PROPERTY_PREFIX);
634     	                } else {
635     	                    propertyName = field.getPropertyName();
636     	                }
637     	                //We don't need to set field if it was already empty
638     	                if (fieldValues.get(propertyName) != null) {
639         	                Object value = this.getDocSearchCriteriaDTOFieldValue(searchCriteria, field.getPropertyName());
640         	                if (value instanceof String
641         	                        && StringUtils.isNotEmpty((String)value)) {
642         	                    field.setPropertyValue(value);
643         	                } else if (value instanceof List){
644         	                    field.setPropertyValues((String[])((List)value).toArray());
645         	                }
646     	                }
647     	            }
648     	        }
649 	        }
650 	    }
651 
652 
653 	/**
654 	 * @see org.kuali.rice.krad.lookup.AbstractLookupableHelperServiceImpl#performClear()
655 	 */
656 	@Override
657 	public void performClear(LookupForm lookupForm) {
658 		//Map<String,String[]> fieldsToClear = new HashMap<String,String[]>();
659 
660 		//for (Row row : this.getRows()) {
661 		//	for (Field field : row.getFields()) {
662 		//		String[] propertyValue = {};
663 		//		if(!Field.MULTI_VALUE_FIELD_TYPES.contains(field.getFieldType())) {
664 		//			propertyValue = new String[]{field.getPropertyValue()};
665 		//		} else {
666 		//			propertyValue = field.getPropertyValues();
667 		//		}
668 		//		fieldsToClear.put(field.getPropertyName(), propertyValue);
669 		//	}
670 		//}
671 
672 	    Map<String,String[]> fixedParameters = new HashMap<String,String[]>();
673         Map<String,String> changedDateFields = preprocessDateFields(lookupForm.getFieldsForLookup());
674         fixedParameters.putAll(this.getParameters());
675         for (Map.Entry<String,String> prop : changedDateFields.entrySet()) {
676             fixedParameters.remove(prop.getKey());
677             fixedParameters.put(prop.getKey(), new String[]{prop.getValue()});
678         }
679 		//TODO: also check if standard here (maybe from object if use criteria)
680 		String docTypeName = fixedParameters.get("docTypeFullName")[0];
681 
682 		DocumentType docType = getValidDocumentType(docTypeName);
683 
684 		if(docType == null) {
685 		    super.performClear(lookupForm);
686 
687 		    // Retrieve the detailed/superuser search statuses.
688 	        boolean detailed=false;
689 	        if(this.getParameters().containsKey("isAdvancedSearch")) {
690 	            detailed = DocSearchCriteriaDTO.ADVANCED_SEARCH_INDICATOR_STRING.equalsIgnoreCase(((String[])this.getParameters().get("isAdvancedSearch"))[0]);
691 	        } else if(fixedParameters.containsKey("isAdvancedSearch")) {
692 	            detailed = DocSearchCriteriaDTO.ADVANCED_SEARCH_INDICATOR_STRING.equalsIgnoreCase((String) fixedParameters.get("isAdvancedSearch")[0]);
693 	        }
694 
695 	        boolean superSearch=false;
696 	        if(this.getParameters().containsKey(("superUserSearch"))) {
697 	            superSearch = DocSearchCriteriaDTO.SUPER_USER_SEARCH_INDICATOR_STRING.equalsIgnoreCase(((String[])this.getParameters().get("superUserSearch"))[0]);
698 	        } else if(fixedParameters.containsKey("superUserSearch")) {
699 	            superSearch = DocSearchCriteriaDTO.SUPER_USER_SEARCH_INDICATOR_STRING.equalsIgnoreCase((String) fixedParameters.get("superUserSearch")[0]);
700 	        }
701 
702 	        // Repopulate the fields indicating detailed/superuser search status.
703 	        int fieldsRepopulated = 0;
704 	        List<Row> rows = super.getRows();
705 	        int index = rows.size() - 1;
706 	        while (index >= 0 && fieldsRepopulated < 2) {
707 	        	for (Field tempField : rows.get(index).getFields()) {
708 	        		if ("isAdvancedSearch".equals(tempField.getPropertyName())) {
709 	        			tempField.setPropertyValue(detailed?"YES":"NO");
710 	        			fieldsRepopulated++;
711 	        		}
712 	        		else if ("superUserSearch".equals(tempField.getPropertyName())) {
713 	        			tempField.setPropertyValue(superSearch?"YES":"NO");
714 	        			fieldsRepopulated++;
715 	        		}
716 	        	}
717 	        	index--;
718 	        }
719 		} else {
720     		DocSearchCriteriaDTO docCriteria = DocumentLookupCriteriaBuilder.populateCriteria(fixedParameters);
721     		docCriteria = docType.getDocumentSearchGenerator().clearSearch(docCriteria);
722             if (docCriteria == null) {
723                 docCriteria = new DocSearchCriteriaDTO();
724             }
725 
726             this.setRowsAfterClear(docCriteria, fixedParameters);
727 		}
728 
729 	}
730 	/**
731 	 *
732 	 * retrieve a document type. This is not a case sensitive search so "TravelRequest" == "Travelrequest"
733 	 *
734 	 * @param docTypeName
735 	 * @return
736 	 */
737     private static DocumentType getValidDocumentType(String docTypeName) {
738     	if (StringUtils.isNotEmpty(docTypeName)) {
739             DocumentType dTypeCriteria = new DocumentType();
740     		dTypeCriteria.setName(docTypeName.trim());
741     		dTypeCriteria.setActive(true);
742     		Collection<DocumentType> docTypeList = KEWServiceLocator.getDocumentTypeService().find(dTypeCriteria, null, false);
743 
744     		// Return the first valid doc type.
745     		DocumentType firstDocumentType = null;
746     		if(docTypeList != null){
747     			for(DocumentType dType: docTypeList){
748     			    if (firstDocumentType == null) {
749     			        firstDocumentType = dType;
750     			    }
751     			    if (StringUtils.equals(docTypeName.toUpperCase(), dType.getName().toUpperCase())) {
752     			        return dType;
753     			    }
754     			}
755     			return firstDocumentType;
756     		}
757     	}
758 
759     	return null;
760     }
761 
762 
763 	/**
764 	 * @see org.kuali.rice.krad.lookup.AbstractLookupableHelperServiceImpl#getSupplementalMenuBar()
765 	 */
766 	@Override
767 	public String getSupplementalMenuBar() {
768 		boolean detailed = false;
769 		if(this.getParameters().containsKey("isAdvancedSearch")) {
770 			detailed = DocSearchCriteriaDTO.ADVANCED_SEARCH_INDICATOR_STRING.equalsIgnoreCase(((String[])this.getParameters().get("isAdvancedSearch"))[0]);
771 		}
772 
773 		boolean superSearch = false;
774 		if(this.getParameters().containsKey("superUserSearch")) {
775 			superSearch = DocSearchCriteriaDTO.SUPER_USER_SEARCH_INDICATOR_STRING.equalsIgnoreCase(((String[])this.getParameters().get("superUserSearch"))[0]);
776 		}
777 
778 		StringBuilder suppMenuBar = new StringBuilder();
779 
780 		// Add the detailed-search-toggling button.
781 		suppMenuBar.append("<input type=\"image\" name=\"methodToCall.customLookupableMethodCall.(((").append(detailed ? "NO" : DocSearchCriteriaDTO.ADVANCED_SEARCH_INDICATOR_STRING).append("))).((`").append(superSearch ? DocSearchCriteriaDTO.SUPER_USER_SEARCH_INDICATOR_STRING : "NO").append(
782 				"`))\" class=\"tinybutton\" src=\"..").append(KEWConstants.WEBAPP_DIRECTORY).append(detailed ? "/images/tinybutton-basicsearch.gif\" alt=\"basic search\" title=\"basic search\" />" : "/images/tinybutton-detailedsearch.gif\" alt=\"detailed search\" title=\"detailed search\" />");
783 
784 		// Add the superuser-search-toggling button.
785 		suppMenuBar.append("&nbsp;").append("<input type=\"image\" name=\"methodToCall.customLookupableMethodCall.(((").append((!detailed && superSearch) ? "NO" : DocSearchCriteriaDTO.ADVANCED_SEARCH_INDICATOR_STRING).append("))).((`").append(superSearch ? "NO" : DocSearchCriteriaDTO.SUPER_USER_SEARCH_INDICATOR_STRING).append(
786 				"`))\" class=\"tinybutton\" src=\"..").append(KEWConstants.WEBAPP_DIRECTORY).append(superSearch ? "/images/tinybutton-nonsupusearch.gif\" alt=\"non-superuser search\" title=\"non-superuser search\" />" : "/images/tinybutton-superusersearch.gif\" alt=\"superuser search\" title=\"superuser search\" />");
787 
788 		// Add the "clear saved searches" button.
789 		suppMenuBar.append("&nbsp;").append("<input type=\"image\" name=\"methodToCall.customLookupableMethodCall.(([true]))\" class=\"tinybutton\" src=\"..").append(KEWConstants.WEBAPP_DIRECTORY).append("/images/tinybutton-clearsavedsearch.gif\" alt=\"clear saved searches\" title=\"clear saved searches\" />");
790 
791         Properties parameters = new Properties();
792         parameters.put(KRADConstants.BUSINESS_OBJECT_CLASS_ATTRIBUTE, this.getBusinessObjectClass().getName());
793         this.getParameters().keySet();
794         for (Object parameter : this.getParameters().keySet()) {
795 			parameters.put(parameter, this.getParameters().get(parameter));
796 		}
797 
798 		UrlFactory.parameterizeUrl(KRADConstants.LOOKUP_ACTION, parameters);
799 		return suppMenuBar.toString();
800 	}
801 
802 //    /**
803 //     * This method is called by performLookup method to generate supplemental action urls.
804 //     * It calls the method getCustomActionUrls to get html data, calls getMaintenanceUrl to get the actual html tag,
805 //     * and returns a formatted/concatenated string of action urls.
806 //     *
807 //     * @see org.kuali.core.lookup.LookupableHelperService#getActionUrls(org.kuali.core.bo.BusinessObject)
808 //     */
809 //    public String getSupplementalActionUrls(List<HtmlData> htmlDataList) {
810 //        StringBuffer actions = new StringBuffer();
811 //
812 //        for(HtmlData htmlData: htmlDataList){
813 //        	actions.append(getMaintenanceUrl(businessObject, htmlData, pkNames, businessObjectRestrictions));
814 //            if(htmlData.getChildUrlDataList()!=null){
815 //            	if(htmlData.getChildUrlDataList().size()>0){
816 //                    actions.append(ACTION_URLS_CHILDREN_STARTER);
817 //            		for(HtmlData childURLData: htmlData.getChildUrlDataList()){
818 //	                	actions.append(getMaintenanceUrl(businessObject, childURLData, pkNames, businessObjectRestrictions));
819 //	                    actions.append(ACTION_URLS_CHILDREN_SEPARATOR);
820 //	            	}
821 //            		if(actions.toString().endsWith(ACTION_URLS_CHILDREN_SEPARATOR))
822 //            			actions.delete(actions.length()-ACTION_URLS_CHILDREN_SEPARATOR.length(), actions.length());
823 //                    actions.append(ACTION_URLS_CHILDREN_END);
824 //            	}
825 //            }
826 //        	actions.append(ACTION_URLS_SEPARATOR);
827 //        }
828 //        if(actions.toString().endsWith(ACTION_URLS_SEPARATOR))
829 //        	actions.delete(actions.length()-ACTION_URLS_SEPARATOR.length(), actions.length());
830 //        return actions.toString();
831 //    }
832 
833 	/**
834 	 * @see org.kuali.rice.krad.lookup.AbstractLookupableHelperServiceImpl#shouldDisplayHeaderNonMaintActions()
835 	 */
836 	@Override
837 	public boolean shouldDisplayHeaderNonMaintActions() {
838 		return this.processor.shouldDisplayHeaderNonMaintActions();
839 	}
840 
841 
842 	/**
843 	 * @see org.kuali.rice.krad.lookup.AbstractLookupableHelperServiceImpl#shouldDisplayLookupCriteria()
844 	 */
845 	@Override
846 	public boolean shouldDisplayLookupCriteria() {
847 		return this.processor.shouldDisplayLookupCriteria();
848 	}
849 
850 
851 	/**
852 	 * @see org.kuali.rice.krad.lookup.AbstractLookupableHelperServiceImpl#checkForAdditionalFields(java.util.Map)
853 	 */
854 	@Override
855 	public boolean checkForAdditionalFields(Map fieldValues) {
856 		// TODO chris - THIS METHOD NEEDS JAVADOCS
857 //		return super.checkForAdditionalFields(fieldValues);
858 		String docTypeName = null;
859 		if(this.getParameters().get("docTypeFullName")!=null) {
860 			docTypeName = ((String[])this.getParameters().get("docTypeFullName"))[0];
861 		}
862 		else if(fieldValues.get("docTypeFullName")!=null) {
863 			docTypeName = (String)fieldValues.get("docTypeFullName");
864 		}
865 		if(!StringUtils.isEmpty(docTypeName)) {
866 		    DocumentType dType = getValidDocumentType(docTypeName);
867 
868             DocSearchCriteriaDTO criteria = DocumentLookupCriteriaBuilder.populateCriteria(getParameters());
869             if (dType != null) {
870 	            MessageMap messages = getValidDocumentType(dType.getName()).getDocumentSearchGenerator().getMessageMap(criteria);
871 	            if (messages != null
872 	                    && messages.hasMessages()) {
873 	                GlobalVariables.mergeErrorMap(messages);
874 	            }
875 	            setRows(fieldValues,dType.getName());
876 	            return true;
877             }
878         }
879 
880 		setRows(fieldValues, docTypeName);
881 
882 		return true;
883 	}
884 
885 	/**
886 	 * @see org.kuali.rice.krad.lookup.AbstractLookupableHelperServiceImpl#validateSearchParameters(java.util.Map)
887 	 */
888 	@Override
889     public void validateSearchParameters(Map fieldValues) {
890         super.validateSearchParameters(fieldValues);
891         DocumentSearchService docSearchService = KEWServiceLocator.getDocumentSearchService();
892         DocSearchCriteriaDTO criteria = DocumentLookupCriteriaBuilder.populateCriteria(getParameters());
893         DocumentType docType = null;
894         if(StringUtils.isNotEmpty(criteria.getDocTypeFullName())) {
895 			docType = getValidDocumentType((String)criteria.getDocTypeFullName());
896 		}
897         DocumentSearchGenerator generator=null;
898         if(docType!=null) {
899         	generator = docType.getDocumentSearchGenerator();
900         } else {
901         	generator = KEWServiceLocator.getDocumentSearchService().getStandardDocumentSearchGenerator();
902         }
903         try {
904         	docSearchService.validateDocumentSearchCriteria(generator, criteria);
905 		} catch (WorkflowServiceErrorException wsee) {
906 			for (WorkflowServiceError workflowServiceError : (List<WorkflowServiceError>)wsee.getServiceErrors()) {
907 				if(workflowServiceError.getMessageMap() != null && workflowServiceError.getMessageMap().hasErrors()){
908 					// merge the message maps
909 					GlobalVariables.getMessageMap().merge(workflowServiceError.getMessageMap());
910 				}else{
911 					//TODO: can we add something to this to get it to highlight the right field too?  Maybe in arg1
912 					GlobalVariables.getMessageMap().putError(workflowServiceError.getMessage(), RiceKeyConstants.ERROR_CUSTOM, workflowServiceError.getMessage());
913 				}
914 			};
915 		}
916         if(!GlobalVariables.getMessageMap().hasNoErrors()) {
917         	throw new ValidationException("errors in search criteria");
918         }
919 
920     }
921 
922 
923 	/**
924 	 * @see org.kuali.rice.krad.lookup.AbstractLookupableHelperServiceImpl#performCustomAction(boolean)
925 	 */
926 	@Override
927 	public boolean performCustomAction(boolean ignoreErrors) {
928 		DocumentSearchService docSearchService = KEWServiceLocator.getDocumentSearchService();
929 
930 		Map<String,String> fieldValues = new HashMap<String,String>();
931 		Map<String,String[]> multFieldValues = new HashMap<String,String[]>();
932 
933 		// Determine if there are any properties embedded in the methodToCall parameter, and retrieve them if so.
934 		String[] methodToCallArray = ((String[])this.getParameters().get(
935                 KRADConstants.DISPATCH_REQUEST_PARAMETER + ".customLookupableMethodCall"));
936 		if (methodToCallArray == null) {
937     		for (String parameter: new ArrayList<String>(this.getParameters().keySet())) {
938     		    String[] parameterSplit = StringUtils.split(parameter, ".");
939     		    String parameterValue = "";
940     		    if (StringUtils.contains(parameter, KRADConstants.DISPATCH_REQUEST_PARAMETER + ".customLookupableMethodCall.")) {
941     		        if (parameter.trim().endsWith(".x")) {
942     		            parameterValue = StringUtils.substringBetween(parameter, KRADConstants.DISPATCH_REQUEST_PARAMETER + ".customLookupableMethodCall.", ".x");
943     		        } else if (parameter.trim().endsWith(".y")) {
944     		            parameterValue = StringUtils.substringBetween(parameter, KRADConstants.DISPATCH_REQUEST_PARAMETER + ".customLookupableMethodCall.", ".y");
945     		        } 
946     		        if (StringUtils.isNotEmpty(parameterValue)) {
947     		            methodToCallArray = new String[]{ parameterValue };
948     	                break;
949     		        }
950     		    }
951     		}
952 		}
953 		//String[] methodToCallArray = ((String[])this.getParameters().get(KRADConstants.DISPATCH_REQUEST_PARAMETER + ".customLookupableMethodCall"));
954 		if (ObjectUtils.isNotNull(methodToCallArray) && methodToCallArray.length > 0) {
955 			String methodToCallVal = methodToCallArray[0];
956 			if (StringUtils.isNotBlank(methodToCallVal)) {
957 				boolean resetRows = false;
958 				// Retrieve the isAdvancedSearch property, if present.
959 				String advancedSearchVal = StringUtils.substringBetween(methodToCallVal, KRADConstants.METHOD_TO_CALL_PARM1_LEFT_DEL, KRADConstants.METHOD_TO_CALL_PARM1_RIGHT_DEL);
960 				if (StringUtils.isNotBlank(advancedSearchVal)) {
961 					if (this.getParameters().containsKey("isAdvancedSearch")) {
962 						((String[]) this.getParameters().get("isAdvancedSearch"))[0] = advancedSearchVal;
963 					}
964 					else {
965 						fieldValues.put("isAdvancedSearch", advancedSearchVal);
966 					}
967 					resetRows = true;
968 				}
969 				// Retrieve the superUserSearch property, if present.
970 				String superUserSearchVal = StringUtils.substringBetween(methodToCallVal, KRADConstants.METHOD_TO_CALL_PARM2_LEFT_DEL, KRADConstants.METHOD_TO_CALL_PARM2_RIGHT_DEL);
971 				if (StringUtils.isNotBlank(superUserSearchVal)) {
972 					if (this.getParameters().containsKey("superUserSearch")) {
973 						((String[]) this.getParameters().get("superUserSearch"))[0] = superUserSearchVal;
974 					} else {
975 						fieldValues.put("superUserSearch", superUserSearchVal);
976 					}
977 					resetRows = true;
978 				}
979 				// Retrieve and act upon the resetSavedSearch property, if present and set to true.
980 				String resetSavedSearchVal = StringUtils.substringBetween(methodToCallVal, KRADConstants.METHOD_TO_CALL_PARM4_LEFT_DEL, KRADConstants.METHOD_TO_CALL_PARM4_RIGHT_DEL);
981 				if (Boolean.parseBoolean(resetSavedSearchVal)) {
982 					docSearchService.clearNamedSearches(GlobalVariables.getUserSession().getPrincipalId());
983 					resetRows = true;
984 				}
985 
986 				// If any of the above properties were found, reset the rows in a manner similar to KualiLookupAction.refresh, but with
987 				// enough modifications to prevent any changed isAdvancedSearch or superUserSearch values from being overridden again.
988 				if (resetRows) {
989 					Map<String,String> values = new HashMap<String,String>();
990 					for (Field tempField : getFields(this.getRows())) {
991 						values.put(tempField.getPropertyName(), tempField.getPropertyValue());
992 					}
993 
994 			        for (Row row : this.getRows()) {
995 			        	for (Field field : row.getFields()) {
996 			        		if (field.getPropertyName() != null && !field.getPropertyName().equals("")) {
997 			        			if (this.getParameters().get(field.getPropertyName()) != null) {
998 			        				if(!Field.MULTI_VALUE_FIELD_TYPES.contains(field.getFieldType())) {
999 			        					field.setPropertyValue(((String[])this.getParameters().get(field.getPropertyName()))[0]);
1000 			        				} else {
1001 			        					//multi value, set to values
1002 			        					field.setPropertyValues((String[])this.getParameters().get(field.getPropertyName()));
1003 			        				}
1004 			        			}
1005 			        		}
1006 			        		else if (values.get(field.getPropertyName()) != null) {
1007 			        			field.setPropertyValue(values.get(field.getPropertyName()));
1008 			        		}
1009 
1010 			        		applyFieldAuthorizationsFromNestedLookups(field);
1011 
1012 			        		fieldValues.put(field.getPropertyName(), field.getPropertyValue());
1013 			        	}
1014 			        }
1015 
1016 			        if (checkForAdditionalFields(fieldValues)) {
1017 			            for (Row row : this.getRows()) {
1018 			                for (Field field : row.getFields()) {
1019 			                    if (field.getPropertyName() != null && !field.getPropertyName().equals("")) {
1020 			                        if (this.getParameters().get(field.getPropertyName()) != null) {
1021 			                        	if(!Field.MULTI_VALUE_FIELD_TYPES.contains(field.getFieldType())) {
1022 			            					field.setPropertyValue(((String[])this.getParameters().get(field.getPropertyName()))[0]);
1023 			            				} else {
1024 			            					//multi value, set to values
1025 			            					field.setPropertyValues((String[])this.getParameters().get(field.getPropertyName()));
1026 			            				}
1027 			            				//FIXME: any reason this is inside this "if" instead of the outer one, like above - this seems inconsistent
1028 			            				fieldValues.put(field.getPropertyName(), ((String[])this.getParameters().get(field.getPropertyName()))[0]);
1029 			                        }
1030 			                        else if (values.get(field.getPropertyName()) != null) {
1031 			                        	field.setPropertyValue(values.get(field.getPropertyName()));
1032 			                        }
1033 			                    }
1034 			                }
1035 			            }
1036 			        }
1037 			        // Finally, return false to prevent the search from being performed and to skip the other custom processing below.
1038 			        return false;
1039 				}
1040 
1041 			}
1042 		} // End of methodToCall parameter retrieval.
1043 
1044 		String[] resetSavedSearch = ((String[])getParameters().get("resetSavedSearch"));
1045 		if(resetSavedSearch!=null) {
1046 			docSearchService.clearNamedSearches(GlobalVariables.getUserSession().getPrincipalId());
1047 			setRows(fieldValues,"");
1048 			return false;
1049 		}
1050 
1051 		String savedSearchName = ((String[])getParameters().get("savedSearchName"))[0];
1052 		if(StringUtils.isEmpty(savedSearchName)||"*ignore*".equals(savedSearchName)) {
1053 			if(!ignoreErrors) {
1054 				GlobalVariables.getMessageMap().putError("savedSearchName", RiceKeyConstants.ERROR_CUSTOM, "You must select a saved search");
1055 			} else {
1056 				//if we're ignoring errors and we got an error just return, no reason to continue.  Also set false to indicate not to perform lookup
1057 				return false;
1058 			}
1059 			//TODO: is there a better way to override a single field value?
1060 			List<Row> rows = this.getRows();
1061 			for (Row row : rows) {
1062 				List<Field> fields = row.getFields();
1063 				for (Field field : fields) {
1064 					if(StringUtils.equals(field.getPropertyName(), "savedSearchName")) {
1065 						field.setPropertyValue("");
1066 						break;
1067 					}
1068 				}
1069 			}
1070 		}
1071         if (!GlobalVariables.getMessageMap().hasNoErrors()) {
1072             throw new ValidationException("errors in search criteria");
1073         }
1074 
1075 
1076 		SavedSearchResult savedSearchResult = null;
1077 		if(StringUtils.isNotEmpty(savedSearchName)) {
1078 			savedSearchResult = docSearchService.getSavedSearchResults(GlobalVariables.getUserSession().getPrincipalId(), savedSearchName);
1079 		}
1080 		DocSearchCriteriaDTO criteria=null;
1081 		if(savedSearchResult!=null){
1082 			criteria = savedSearchResult.getDocSearchCriteriaDTO();
1083 		}
1084 		//put the doc type from the criteria in the map to send to setRows and then call setRows
1085 		String docTypeName = criteria.getDocTypeFullName();
1086 
1087 
1088 		setRows(fieldValues,docTypeName);
1089 
1090 		//set field values (besides search atts) here
1091 //		fieldValues.put("docTypeFullName", docTypeName);
1092 //		fieldValues.put("fromDateCreated", criteria.getFromDateCreated());
1093 		fieldValues.put("savedSearchName", "");
1094 
1095 
1096 		for (SearchAttributeCriteriaComponent searchAtt : criteria.getSearchableAttributes()) {
1097 			if(!searchAtt.isCanHoldMultipleValues()) {
1098 				fieldValues.put(searchAtt.getSavedKey(), searchAtt.getValue());
1099 			} else {
1100 				List<String> values = searchAtt.getValues();
1101 				String[] arrayValues = {};
1102 				arrayValues = values.toArray(arrayValues);
1103 				multFieldValues.put(searchAtt.getSavedKey(), arrayValues);
1104 			}
1105 
1106 		}
1107 		Object fieldValue = null;
1108         for (Row row2 : getRows()) {
1109             Row row = (Row) row2;
1110             for (Field field2 : row.getFields()) {
1111                 Field field = (Field) field2;
1112                 if (field.getPropertyName() != null && !field.getPropertyName().equals("")) {
1113                 	if (fieldValues.get(field.getPropertyName()) != null) {
1114         				field.setPropertyValue(fieldValues.get(field.getPropertyName()));
1115                     } else if(multFieldValues.get(field.getPropertyName())!=null){
1116          				field.setPropertyValues(multFieldValues.get(field.getPropertyName()));
1117                     } else {
1118                     	//may be on the root of the criteria object, try looking there:
1119                     	try {
1120 							fieldValue = PropertyUtils.getProperty(criteria, field.getPropertyName());
1121 						} catch (IllegalAccessException e) {
1122 							e.printStackTrace();
1123 						} catch (InvocationTargetException e) {
1124 							e.printStackTrace();
1125 						} catch (NoSuchMethodException e) {
1126 //							e.printStackTrace();
1127 							//hmm what to do here, we should be able to find everything either in the search atts or at the base as far as I know.
1128 						}
1129 						if(fieldValue!=null) {
1130 							field.setPropertyValue(fieldValue);
1131 						}
1132 
1133                     }
1134                 }
1135             }
1136         }
1137         //indicate to subsequent actions (search in this case) that a saved search was just populated
1138         savedSearch=true;
1139 
1140         return true;
1141 	}
1142 
1143 	/**
1144 	 * @see org.kuali.rice.krad.lookup.AbstractLookupableHelperServiceImpl#getExtraField()
1145 	 */
1146 	@Override
1147 	public Field getExtraField() {
1148 		SavedSearchValuesFinder savedSearchValuesFinder = new SavedSearchValuesFinder();
1149 		List<KeyValue> savedSearchValues = savedSearchValuesFinder.getKeyValues();
1150 
1151 		Field savedSearch = new Field();
1152 		savedSearch.setPropertyName("savedSearchName");
1153 		savedSearch.setFieldType(Field.DROPDOWN_SCRIPT);
1154 		savedSearch.setScript("customLookupChanged()");
1155 		savedSearch.setFieldValidValues(savedSearchValues);
1156 		savedSearch.setFieldLabel("Saved Searches");
1157 		return savedSearch;
1158 
1159 	}
1160 
1161 	private Object getDocSearchCriteriaDTOFieldValue (DocSearchCriteriaDTO searchCriteria, String fieldName) {
1162         Class<?> clazz = searchCriteria.getClass();
1163         String propertyName = fieldName;
1164         if(fieldName.startsWith(KRADConstants.LOOKUP_RANGE_LOWER_BOUND_PROPERTY_PREFIX)) {
1165             propertyName = StringUtils.remove(fieldName, KRADConstants.LOOKUP_RANGE_LOWER_BOUND_PROPERTY_PREFIX);
1166         }
1167         try {
1168             String methodName = new StringBuffer("get").append(propertyName.toUpperCase().charAt(0)).append(propertyName.substring(1)).toString();
1169             java.lang.reflect.Method method = clazz.getMethod(methodName);
1170             return method.invoke(searchCriteria);
1171         } catch (SecurityException e) {
1172             return null;
1173         } catch (IllegalArgumentException e) {
1174             return null;
1175         } catch (IllegalAccessException e) {
1176             return null;
1177         } catch (InvocationTargetException e) {
1178             return null;
1179         } catch (NoSuchMethodException e) {
1180             return getSearchableAttributeFieldValue(searchCriteria, fieldName);
1181         }
1182     }
1183 
1184 	private Object getSearchableAttributeFieldValue(DocSearchCriteriaDTO searchCriteria, String fieldName) {
1185 	    Object valueToReturn = null;
1186 	    String propertyName = fieldName;
1187 	    boolean isDateTime = false;
1188         if(fieldName.startsWith(KRADConstants.LOOKUP_RANGE_LOWER_BOUND_PROPERTY_PREFIX)) {
1189             propertyName = StringUtils.remove(fieldName, KRADConstants.LOOKUP_RANGE_LOWER_BOUND_PROPERTY_PREFIX);
1190         }
1191 	    if (searchCriteria.getSearchableAttributes() != null) {
1192     	    for (SearchAttributeCriteriaComponent sa : searchCriteria.getSearchableAttributes()) {
1193     	        if (StringUtils.equals(propertyName, sa.getFormKey())) {
1194     	            if (StringUtils.equals(sa.getSearchableAttributeValue().getOjbConcreteClass(), "org.kuali.rice.kew.docsearch.SearchableAttributeDateTimeValue")) {
1195     	                isDateTime = true;
1196     	            }
1197     	            if (sa.isCanHoldMultipleValues()) {
1198     	                valueToReturn = sa.getValues();
1199     	            } else {
1200     	                valueToReturn = sa.getValue();
1201     	            }
1202     	            break;
1203     	        }
1204     	    }
1205 	    }
1206 
1207 	    if (valueToReturn != null
1208 	            && valueToReturn instanceof String
1209 	            && isDateTime) {
1210 	        if(fieldName.startsWith(KRADConstants.LOOKUP_RANGE_LOWER_BOUND_PROPERTY_PREFIX)) {
1211     	        if (StringUtils.contains((String)valueToReturn, SearchOperator.BETWEEN.op())) {
1212     	            valueToReturn = StringUtils.split((String)valueToReturn, SearchOperator.BETWEEN.op())[0];
1213     	        } else if (StringUtils.contains((String)valueToReturn, SearchOperator.GREATER_THAN_EQUAL.op())) {
1214     	            valueToReturn = StringUtils.split((String)valueToReturn, SearchOperator.GREATER_THAN_EQUAL.op())[0];
1215     	        } else {
1216     	            valueToReturn = null;
1217     	        }
1218 	        } else {
1219 	            if (StringUtils.contains((String)valueToReturn, SearchOperator.BETWEEN.op())) {
1220                     valueToReturn = StringUtils.split((String)valueToReturn, SearchOperator.BETWEEN.op())[1];
1221                 } else if (StringUtils.contains((String)valueToReturn, SearchOperator.GREATER_THAN_EQUAL.op())) {
1222                     valueToReturn = null;
1223                 } else if (StringUtils.contains((String)valueToReturn, SearchOperator.LESS_THAN_EQUAL.op())) {
1224                     valueToReturn = StringUtils.split((String)valueToReturn, SearchOperator.LESS_THAN_EQUAL.op())[0];
1225                 }
1226 
1227 	        }
1228 	    }
1229 
1230 	    return valueToReturn;
1231 	}
1232 
1233 	/*
1234     @Override
1235     public List<Row> getRows() {
1236         if(StringUtils.isEmpty(docTypeName)) {
1237             super.performClear(lookupForm);
1238         } else {
1239             DocSearchCriteriaDTO docCriteria = DocumentLookupCriteriaBuilder.populateCriteria(fieldsToClear);
1240             //TODO: Chris - (2 stage clear) set the isOnlyDocTypeFilled, to true if only doc type coming in (besides hidden) and false otherwise)
1241             docCriteria = getValidDocumentType(docTypeName).getDocumentSearchGenerator().clearSearch(docCriteria);
1242         }
1243 
1244         return super.getRows();
1245     }
1246 	*/
1247 
1248 }