View Javadoc
1   package org.kuali.ole;
2   
3   import org.apache.commons.beanutils.PropertyUtils;
4   import org.apache.commons.lang.StringUtils;
5   import org.kuali.rice.core.api.CoreApiServiceLocator;
6   import org.kuali.rice.core.api.config.property.ConfigurationService;
7   import org.kuali.rice.core.api.encryption.EncryptionService;
8   import org.kuali.rice.core.api.search.SearchOperator;
9   import org.kuali.rice.core.api.util.RiceKeyConstants;
10  import org.kuali.rice.core.api.util.type.TypeUtils;
11  import org.kuali.rice.kim.api.identity.Person;
12  import org.kuali.rice.krad.bo.ExternalizableBusinessObject;
13  import org.kuali.rice.krad.datadictionary.BusinessObjectEntry;
14  import org.kuali.rice.krad.datadictionary.RelationshipDefinition;
15  import org.kuali.rice.krad.lookup.*;
16  import org.kuali.rice.krad.service.*;
17  import org.kuali.rice.krad.uif.UifConstants;
18  import org.kuali.rice.krad.uif.UifParameters;
19  import org.kuali.rice.krad.uif.UifPropertyPaths;
20  import org.kuali.rice.krad.uif.control.Control;
21  import org.kuali.rice.krad.uif.control.HiddenControl;
22  import org.kuali.rice.krad.uif.control.ValueConfiguredControl;
23  import org.kuali.rice.krad.uif.element.Action;
24  import org.kuali.rice.krad.uif.field.InputField;
25  import org.kuali.rice.krad.uif.field.LookupInputField;
26  import org.kuali.rice.krad.uif.service.impl.ViewHelperServiceImpl;
27  import org.kuali.rice.krad.uif.util.ComponentUtils;
28  import org.kuali.rice.krad.uif.util.LookupInquiryUtils;
29  import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
30  import org.kuali.rice.krad.uif.view.LookupView;
31  import org.kuali.rice.krad.uif.view.View;
32  import org.kuali.rice.krad.util.*;
33  import org.kuali.rice.krad.web.form.LookupForm;
34  
35  import java.security.GeneralSecurityException;
36  import java.util.*;
37  
38  /**
39   * Created by pvsubrah on 5/5/14.
40   */
41  
42  //TODO: This class will need to be retired once there is a permanent fix for the securefieldpaternmatching
43  //TODO: in KRADUtils thats used bu the LookupableImpl
44  
45  public class OleLookupableImpl extends ViewHelperServiceImpl implements Lookupable {
46      private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(LookupableImpl.class);
47  
48      private Class<?> dataObjectClass;
49  
50      private transient ConfigurationService configurationService;
51      private transient DataObjectAuthorizationService dataObjectAuthorizationService;
52      private transient DataObjectMetaDataService dataObjectMetaDataService;
53      private transient DocumentDictionaryService documentDictionaryService;
54      private transient LookupService lookupService;
55      private transient EncryptionService encryptionService;
56  
57      /**
58       * Initialization of Lookupable requires that the business object class be set for the
59       * {@link #initializeDataFieldFromDataDictionary(org.kuali.rice.krad.uif.view.View,
60       * org.kuali.rice.krad.uif.field.DataField)} method
61       *
62       * @see org.kuali.rice.krad.uif.service.impl.ViewHelperServiceImpl#performInitialization(org.kuali.rice.krad.uif.view.View,
63       *      java.lang.Object)
64       */
65      @Override
66      public void performInitialization(View view, Object model) {
67          if (!LookupView.class.isAssignableFrom(view.getClass())) {
68              throw new IllegalArgumentException(
69                      "View class '" + view.getClass() + " is not assignable from the '" + LookupView.class + "'");
70          }
71  
72          LookupView lookupView = (LookupView) view;
73          setDataObjectClass(lookupView.getDataObjectClassName());
74  
75          super.performInitialization(view, model);
76      }
77  
78      /**
79       * @see org.kuali.rice.krad.lookup.Lookupable#initSuppressAction(org.kuali.rice.krad.web.form.LookupForm)
80       */
81      @Override
82      public void initSuppressAction(LookupForm lookupForm) {
83          LookupViewAuthorizerBase lookupAuthorizer = (LookupViewAuthorizerBase) lookupForm.getView().getAuthorizer();
84          Person user = GlobalVariables.getUserSession().getPerson();
85          ((LookupView) lookupForm.getView()).setSuppressActions(!lookupAuthorizer.canInitiateDocument(lookupForm, user));
86      }
87  
88      /**
89       * @see org.kuali.rice.krad.lookup.Lookupable#performSearch
90       */
91      @Override
92      public Collection<?> performSearch(LookupForm form, Map<String, String> searchCriteria, boolean bounded) {
93          Collection<?> displayList;
94  
95          // TODO: force uppercase will be done in binding at some point
96          displayList = getSearchResults(form, LookupUtils.forceUppercase(getDataObjectClass(), searchCriteria),
97                  !bounded);
98  
99          // TODO delyea - is this the best way to set that the entire set has a returnable row?
100         for (Object object : displayList) {
101             if (isResultReturnable(object)) {
102                 form.setAtLeastOneRowReturnable(true);
103             }
104         }
105 
106         return displayList;
107     }
108 
109     /**
110      * Get the search results of the lookup
111      *
112      * @param form lookup form instance containing the lookup data
113      * @param searchCriteria map of criteria currently set
114      * @param unbounded indicates whether the complete result should be returned.  When set to false the result is
115      * limited (if necessary) to the max search result limit configured.
116      * @return the list of result objects, possibly bounded
117      */
118     protected List<?> getSearchResults(LookupForm form, Map<String, String> searchCriteria, boolean unbounded) {
119         Collection<?> searchResults;
120 
121         // removed blank search values and decrypt any encrypted search values
122         Map<String, String> nonBlankSearchCriteria = processSearchCriteria(form, searchCriteria);
123 
124         // return empty search results (none found) when the search doesn't have any nonBlankSearchCriteria although
125         // a filtered search criteria is specified
126         if (nonBlankSearchCriteria == null) {
127             return new ArrayList<Object>();
128         }
129 
130         // if this class is an EBO, just call the module service to get the results
131         if (ExternalizableBusinessObject.class.isAssignableFrom(getDataObjectClass())) {
132             return getSearchResultsForEBO(nonBlankSearchCriteria, unbounded);
133         }
134 
135         // if any of the properties refer to an embedded EBO, call the EBO
136         // lookups first and apply to the local lookup
137         try {
138             Integer searchResultsLimit = null;
139 
140             if (!unbounded) {
141                 searchResultsLimit = LookupUtils.getSearchResultsLimit(getDataObjectClass(), form);
142             }
143 
144             if (LookupUtils.hasExternalBusinessObjectProperty(getDataObjectClass(), nonBlankSearchCriteria)) {
145                 Map<String, String> eboSearchCriteria = adjustCriteriaForNestedEBOs(nonBlankSearchCriteria, unbounded);
146 
147                 if (LOG.isDebugEnabled()) {
148                     LOG.debug("Passing these results into the lookup service: " + eboSearchCriteria);
149                 }
150 
151                 // add those results as criteria run the normal search (but with the EBO criteria added)
152                 searchResults = getLookupService().findCollectionBySearchHelper(getDataObjectClass(), eboSearchCriteria,
153                         unbounded, searchResultsLimit);
154                 generateLookupResultsMessages(form, eboSearchCriteria, searchResults, unbounded);
155             } else {
156                 searchResults = getLookupService().findCollectionBySearchHelper(getDataObjectClass(),
157                         nonBlankSearchCriteria, unbounded, searchResultsLimit);
158                 generateLookupResultsMessages(form, nonBlankSearchCriteria, searchResults, unbounded);
159             }
160 
161         } catch (IllegalAccessException e) {
162             throw new RuntimeException("Error trying to perform search", e);
163         } catch (InstantiationException e1) {
164             throw new RuntimeException("Error trying to perform search", e1);
165         }
166 
167         if (searchResults == null) {
168             searchResults = new ArrayList<Object>();
169         } else {
170             sortSearchResults(form, (List<?>) searchResults);
171         }
172 
173         return (List<?>) searchResults;
174     }
175 
176     /**
177      * Convenience method for setting an error message on the lookup results section
178      *
179      * @param form
180      * @param messageToDisplay
181      */
182     public void generateErrorMessageForResults(LookupForm form, String messageToDisplay) {
183         GlobalVariables.getMessageMap().putErrorForSectionId("LookupResultMessages", messageToDisplay);
184     }
185 
186     /**
187      * Helper function to render lookup results messages
188      *
189      * @param form
190      * @param searchCriteria
191      * @param searchResult
192      * @param unbounded
193      */
194     protected void generateLookupResultsMessages(LookupForm form, Map<String, String> searchCriteria,
195                                                  Collection<?> searchResult, boolean unbounded) {
196         String resultsPropertyName = "LookupResultMessages";
197         List<String> pkLabels = new ArrayList<String>();
198 
199         Boolean usingPrimaryKey = getLookupService().allPrimaryKeyValuesPresentAndNotWildcard(getDataObjectClass(),
200                 (Map<String, String>) searchCriteria);
201 
202         Integer searchResultsLimit = LookupUtils.getSearchResultsLimit(getDataObjectClass(), form);
203         Long searchResultsSize = Long.valueOf(0);
204 
205         if (searchResult instanceof CollectionIncomplete
206                 && ((CollectionIncomplete) searchResult).getActualSizeIfTruncated() > 0) {
207             searchResultsSize = ((CollectionIncomplete) searchResult).getActualSizeIfTruncated();
208         } else if (searchResult != null) {
209             searchResultsSize = Long.valueOf(searchResult.size());
210         }
211 
212         Boolean resultsExceedsLimit = !unbounded
213                 && searchResultsLimit != null
214                 && searchResultsSize > 0
215                 && searchResultsSize > searchResultsLimit ? true : false;
216 
217         if (usingPrimaryKey) {
218             List<String> pkNames = getDataObjectMetaDataService().listPrimaryKeyFieldNames(getDataObjectClass());
219             for (String pkName : pkNames) {
220                 pkLabels.add(getDataDictionaryService().getAttributeLabel(getDataObjectClass(), pkName));
221             }
222 
223             GlobalVariables.getMessageMap().putInfoForSectionId(resultsPropertyName,
224                     RiceKeyConstants.INFO_LOOKUP_RESULTS_USING_PRIMARY_KEY, StringUtils.join(pkLabels, ","));
225         }
226 
227         if (searchResultsSize == 0) {
228             GlobalVariables.getMessageMap().putInfoForSectionId(resultsPropertyName,
229                     RiceKeyConstants.INFO_LOOKUP_RESULTS_NONE_FOUND);
230         } else if (searchResultsSize == 1) {
231             GlobalVariables.getMessageMap().putInfoForSectionId(resultsPropertyName,
232                     RiceKeyConstants.INFO_LOOKUP_RESULTS_DISPLAY_ONE);
233         } else if (searchResultsSize > 1) {
234             if (resultsExceedsLimit) {
235                 GlobalVariables.getMessageMap().putInfoForSectionId(resultsPropertyName,
236                         RiceKeyConstants.INFO_LOOKUP_RESULTS_EXCEEDS_LIMIT, searchResultsSize.toString(),
237                         searchResultsLimit.toString());
238             } else {
239                 GlobalVariables.getMessageMap().putInfoForSectionId(resultsPropertyName,
240                         RiceKeyConstants.INFO_LOOKUP_RESULTS_DISPLAY_ALL, searchResultsSize.toString());
241             }
242         }
243     }
244 
245     /**
246      * Sorts the given list of search results based on the lookup view's configured sort attributes
247      *
248      * <p>
249      * First if the posted view exists we grab the sort attributes from it. This will take into account expressions
250      * that might have been configured on the sort attributes. If the posted view does not exist (because we did a
251      * search from a get request or form session storage is off), we get the sort attributes from the view that we
252      * will be rendered (and was initialized before controller call). However, expressions will not be evaluated yet,
253      * thus if expressions were configured we don't know the results and can not sort the list
254      * </p>
255      *
256      * @param form - lookup form instance containing view information
257      * @param searchResults - list of search results to sort
258      * @TODO: revisit this when we have a solution for the posted view problem
259      */
260     protected void sortSearchResults(LookupForm form, List<?> searchResults) {
261         List<String> defaultSortColumns = null;
262         boolean defaultSortAscending = true;
263         // first choice is to get default sort columns off posted view, since that will include the full
264         // lifecycle and expression evaluations
265         if (form.getPostedView() != null) {
266             defaultSortColumns = ((LookupView) form.getPostedView()).getDefaultSortAttributeNames();
267             defaultSortAscending = ((LookupView) form.getPostedView()).isDefaultSortAscending();
268         }
269         // now try view being built, if default sort attributes have any expression (entry is null) we can't use them
270         else if (form.getView() != null) {
271             defaultSortColumns = ((LookupView) form.getView()).getDefaultSortAttributeNames();
272             defaultSortAscending = ((LookupView) form.getView()).isDefaultSortAscending();
273             boolean hasExpression = false;
274             if (defaultSortColumns != null) {
275                 for (String sortColumn : defaultSortColumns) {
276                     if (sortColumn == null) {
277                         hasExpression = true;
278                     }
279                 }
280             }
281 
282             if (hasExpression) {
283                 defaultSortColumns = null;
284             }
285         }
286 
287         if ((defaultSortColumns != null) && (!defaultSortColumns.isEmpty())) {
288             BeanPropertyComparator comparator = new BeanPropertyComparator(defaultSortColumns, true);
289             if (defaultSortAscending) {
290                 Collections.sort(searchResults, comparator);
291             } else {
292                 Collections.sort(searchResults, Collections.reverseOrder(comparator));
293             }
294         }
295     }
296 
297     /**
298      * Process the search criteria to be used with the lookup
299      *
300      * <p>
301      * Processing entails primarily of the removal of filtered and unused/blank search criteria.  Encrypted field
302      * values are decrypted in this process as well.
303      * </p>
304      *
305      * @param lookupForm lookup form instance containing the lookup data
306      * @param searchCriteria map of criteria currently set
307      * @return map with the non blank search criteria
308      */
309     protected Map<String, String> processSearchCriteria(LookupForm lookupForm, Map<String, String> searchCriteria) {
310         Map<String, InputField> criteriaFields = new HashMap<String, InputField>();
311         if (lookupForm.getPostedView() != null) {
312             criteriaFields = getCriteriaFieldsForValidation((LookupView) lookupForm.getPostedView(), lookupForm);
313         }
314 
315         Map<String, String> filteredSearchCriteria = new HashMap<String, String>(searchCriteria);
316         for (String fieldName : searchCriteria.keySet()) {
317             InputField inputField = criteriaFields.get(fieldName);
318             if ((inputField == null) || !(inputField instanceof LookupInputField)) {
319                 continue;
320             }
321 
322             filteredSearchCriteria = ((LookupInputField) inputField).filterSearchCriteria(filteredSearchCriteria);
323             if (filteredSearchCriteria == null) {
324                 return null;
325             }
326         }
327 
328         Map<String, String> nonBlankSearchCriteria = new HashMap<String, String>();
329         for (String fieldName : filteredSearchCriteria.keySet()) {
330             String fieldValue = filteredSearchCriteria.get(fieldName);
331 
332             // don't add hidden criteria
333             InputField inputField = criteriaFields.get(fieldName);
334             if ((inputField != null) && (inputField.getControl() instanceof HiddenControl)) {
335                 continue;
336             }
337 
338             // only add criteria if non blank
339             if (StringUtils.isNotBlank(fieldValue)) {
340                 if (fieldValue.endsWith(EncryptionService.ENCRYPTION_POST_PREFIX)) {
341                     String encryptedValue = StringUtils.removeEnd(fieldValue, EncryptionService.ENCRYPTION_POST_PREFIX);
342                     try {
343                         if (CoreApiServiceLocator.getEncryptionService().isEnabled()) {
344                             fieldValue = getEncryptionService().decrypt(encryptedValue);
345                         }
346                     } catch (GeneralSecurityException e) {
347                         LOG.error("Error decrypting value for business object class " + getDataObjectClass() +
348                                 " attribute " + fieldName, e);
349                         throw new RuntimeException(
350                                 "Error decrypting value for business object class " + getDataObjectClass() +
351                                         " attribute " + fieldName, e);
352                     }
353                 }
354 
355                 nonBlankSearchCriteria.put(fieldName, fieldValue);
356             }
357         }
358 
359         return nonBlankSearchCriteria;
360     }
361 
362     /**
363      * Get the search results of an {@linkExternalizableBusinessObject}
364      *
365      * @param searchCriteria map of criteria currently set
366      * @param unbounded indicates whether the complete result should be returned.  When set to false the result is
367      * limited (if necessary) to the max search result limit configured.
368      * @return list of result objects, possibly bounded
369      */
370     protected List<?> getSearchResultsForEBO(Map<String, String> searchCriteria, boolean unbounded) {
371         ModuleService eboModuleService = KRADServiceLocatorWeb.getKualiModuleService().getResponsibleModuleService(
372                 getDataObjectClass());
373         BusinessObjectEntry ddEntry = eboModuleService.getExternalizableBusinessObjectDictionaryEntry(
374                 getDataObjectClass());
375 
376         Map<String, String> filteredFieldValues = new HashMap<String, String>();
377         for (String fieldName : searchCriteria.keySet()) {
378             if (ddEntry.getAttributeNames().contains(fieldName)) {
379                 filteredFieldValues.put(fieldName, searchCriteria.get(fieldName));
380             }
381         }
382 
383         List<?> searchResults = eboModuleService.getExternalizableBusinessObjectsListForLookup(
384                 (Class<? extends ExternalizableBusinessObject>) getDataObjectClass(), (Map) filteredFieldValues,
385                 unbounded);
386 
387         return searchResults;
388     }
389 
390     /**
391      * @param searchCriteria map of criteria currently set
392      * @param unbounded indicates whether the complete result should be returned.  When set to false the result is
393      * limited (if necessary) to the max search result limit configured.
394      * @return
395      * @throws InstantiationException
396      * @throws IllegalAccessException
397      */
398     protected Map<String, String> adjustCriteriaForNestedEBOs(Map<String, String> searchCriteria,
399                                                               boolean unbounded) throws InstantiationException, IllegalAccessException {
400         if (LOG.isDebugEnabled()) {
401             LOG.debug("has EBO reference: " + getDataObjectClass());
402             LOG.debug("properties: " + searchCriteria);
403         }
404 
405         // remove the EBO criteria
406         Map<String, String> nonEboFieldValues = LookupUtils.removeExternalizableBusinessObjectFieldValues(
407                 getDataObjectClass(), searchCriteria);
408         if (LOG.isDebugEnabled()) {
409             LOG.debug("Non EBO properties removed: " + nonEboFieldValues);
410         }
411 
412         // get the list of EBO properties attached to this object
413         List<String> eboPropertyNames = LookupUtils.getExternalizableBusinessObjectProperties(getDataObjectClass(),
414                 searchCriteria);
415         if (LOG.isDebugEnabled()) {
416             LOG.debug("EBO properties: " + eboPropertyNames);
417         }
418 
419         // loop over those properties
420         for (String eboPropertyName : eboPropertyNames) {
421             // extract the properties as known to the EBO
422             Map<String, String> eboFieldValues = LookupUtils.getExternalizableBusinessObjectFieldValues(eboPropertyName,
423                     searchCriteria);
424             if (LOG.isDebugEnabled()) {
425                 LOG.debug("EBO properties for master EBO property: " + eboPropertyName);
426                 LOG.debug("properties: " + eboFieldValues);
427             }
428 
429             // run search against attached EBO's module service
430             ModuleService eboModuleService = KRADServiceLocatorWeb.getKualiModuleService().getResponsibleModuleService(
431                     LookupUtils.getExternalizableBusinessObjectClass(getDataObjectClass(), eboPropertyName));
432 
433             // KULRICE-4401 made eboResults an empty list and only filled if
434             // service is found.
435             List<?> eboResults = Collections.emptyList();
436             if (eboModuleService != null) {
437                 eboResults = eboModuleService.getExternalizableBusinessObjectsListForLookup(
438                         LookupUtils.getExternalizableBusinessObjectClass(getDataObjectClass(), eboPropertyName),
439                         (Map) eboFieldValues, unbounded);
440             } else {
441                 LOG.debug("EBO ModuleService is null: " + eboPropertyName);
442             }
443             // get the mapping/relationship between the EBO object and it's
444             // parent object
445             // use that to adjust the searchCriteria
446 
447             // get the parent property type
448             Class<?> eboParentClass;
449             String eboParentPropertyName;
450             if (ObjectUtils.isNestedAttribute(eboPropertyName)) {
451                 eboParentPropertyName = StringUtils.substringBeforeLast(eboPropertyName, ".");
452                 try {
453                     eboParentClass = PropertyUtils.getPropertyType(getDataObjectClass().newInstance(),
454                             eboParentPropertyName);
455                 } catch (Exception ex) {
456                     throw new RuntimeException(
457                             "Unable to create an instance of the business object class: " + getDataObjectClass()
458                                     .getName(), ex);
459                 }
460             } else {
461                 eboParentClass = getDataObjectClass();
462                 eboParentPropertyName = null;
463             }
464 
465             if (LOG.isDebugEnabled()) {
466                 LOG.debug("determined EBO parent class/property name: " + eboParentClass + "/" + eboParentPropertyName);
467             }
468 
469             // look that up in the DD (BOMDS)
470             // find the appropriate relationship
471             // CHECK THIS: what if eboPropertyName is a nested attribute -
472             // need to strip off the eboParentPropertyName if not null
473             RelationshipDefinition rd = getDataObjectMetaDataService().getDictionaryRelationship(eboParentClass,
474                     eboPropertyName);
475             if (LOG.isDebugEnabled()) {
476                 LOG.debug("Obtained RelationshipDefinition for " + eboPropertyName);
477                 LOG.debug(rd);
478             }
479 
480             // copy the needed properties (primary only) to the field values KULRICE-4446 do
481             // so only if the relationship definition exists
482             // NOTE: this will work only for single-field PK unless the ORM
483             // layer is directly involved
484             // (can't make (field1,field2) in ( (v1,v2),(v3,v4) ) style
485             // queries in the lookup framework
486             if (ObjectUtils.isNotNull(rd)) {
487                 if (rd.getPrimitiveAttributes().size() > 1) {
488                     throw new RuntimeException(
489                             "EBO Links don't work for relationships with multiple-field primary keys.");
490                 }
491                 String boProperty = rd.getPrimitiveAttributes().get(0).getSourceName();
492                 String eboProperty = rd.getPrimitiveAttributes().get(0).getTargetName();
493                 StringBuffer boPropertyValue = new StringBuffer();
494 
495                 // loop over the results, making a string that the lookup
496                 // DAO will convert into an
497                 // SQL "IN" clause
498                 for (Object ebo : eboResults) {
499                     if (boPropertyValue.length() != 0) {
500                         boPropertyValue.append(SearchOperator.OR.op());
501                     }
502                     try {
503                         boPropertyValue.append(PropertyUtils.getProperty(ebo, eboProperty).toString());
504                     } catch (Exception ex) {
505                         LOG.warn("Unable to get value for " + eboProperty + " on " + ebo);
506                     }
507                 }
508 
509                 if (eboParentPropertyName == null) {
510                     // non-nested property containing the EBO
511                     nonEboFieldValues.put(boProperty, boPropertyValue.toString());
512                 } else {
513                     // property nested within the main searched-for BO that
514                     // contains the EBO
515                     nonEboFieldValues.put(eboParentPropertyName + "." + boProperty, boPropertyValue.toString());
516                 }
517             }
518         }
519 
520         return nonEboFieldValues;
521     }
522 
523     /**
524      * @see org.kuali.rice.krad.lookup.Lookupable#performClear
525      */
526     @Override
527     public Map<String, String> performClear(LookupForm form, Map<String, String> searchCriteria) {
528         Map<String, InputField> criteriaFieldMap = new HashMap<String, InputField>();
529         if (form.getPostedView() == null) {
530             criteriaFieldMap = getCriteriaFieldsForValidation((LookupView) form.getPostedView(), form);
531         }
532 
533         List<String> readOnlyFieldsList = form.getReadOnlyFieldsList();
534 
535         Map<String, String> clearedSearchCriteria = new HashMap<String, String>();
536         for (Map.Entry<String, String> searchKeyValue : searchCriteria.entrySet()) {
537             String searchPropertyName = searchKeyValue.getKey();
538 
539             InputField inputField = criteriaFieldMap.get(searchPropertyName);
540 
541             if (readOnlyFieldsList != null && readOnlyFieldsList.contains(searchPropertyName)) {
542                 clearedSearchCriteria.put(searchPropertyName, searchKeyValue.getValue());
543             } else if (inputField != null) {
544                 // TODO: check secure fields
545                 //                                if (field.isSecure()) {
546                 //                    field.setSecure(false);
547                 //                    field.setDisplayMaskValue(null);
548                 //                    field.setEncryptedValue(null);
549                 //                }
550 
551                 // TODO: need formatting on default value and make sure it works when control converts
552                 // from checkbox to radio
553                 clearedSearchCriteria.put(searchPropertyName, inputField.getDefaultValue());
554             } else {
555                 clearedSearchCriteria.put(searchPropertyName, "");
556             }
557         }
558 
559         return clearedSearchCriteria;
560     }
561 
562     /**
563      * @see org.kuali.rice.krad.lookup.Lookupable#validateSearchParameters
564      */
565     @Override
566     public boolean validateSearchParameters(LookupForm form, Map<String, String> searchCriteria) {
567         boolean valid = true;
568 
569         // if postedView is null then we are executing the search from get request, in which case we
570         // can't validate the criteria
571         if (form.getPostedView() == null) {
572             return valid;
573         }
574 
575         Map<String, InputField> criteriaFields = getCriteriaFieldsForValidation((LookupView) form.getPostedView(),
576                 form);
577 
578         // build list of hidden properties configured with criteria fields
579         List<String> hiddenCriteria = new ArrayList<String>();
580         for (InputField field : criteriaFields.values()) {
581             if (field.getAdditionalHiddenPropertyNames() != null) {
582                 hiddenCriteria.addAll(field.getAdditionalHiddenPropertyNames());
583             }
584         }
585 
586         // validate required
587         // TODO: this will be done by the uif validation service at some point
588         for (Map.Entry<String, String> searchKeyValue : searchCriteria.entrySet()) {
589             String searchPropertyName = searchKeyValue.getKey();
590             String searchPropertyValue = searchKeyValue.getValue();
591 
592             InputField inputField = criteriaFields.get(searchPropertyName);
593 
594             String adjustedSearchPropertyPath = UifPropertyPaths.LOOKUP_CRITERIA + "[" + searchPropertyName + "]";
595             if (inputField == null && hiddenCriteria.contains(adjustedSearchPropertyPath)) {
596                 return valid;
597             }
598 
599             // verify the property sent is a valid to search on
600             if ((inputField == null) && !searchPropertyName.contains(
601                     KRADConstants.LOOKUP_RANGE_LOWER_BOUND_PROPERTY_PREFIX)) {
602                 throw new RuntimeException("Invalid search field sent for property name: " + searchPropertyName);
603             }
604 
605             if (inputField != null) {
606                 if (StringUtils.isBlank(searchPropertyValue) && inputField.getRequired()) {
607                     GlobalVariables.getMessageMap().putError(inputField.getPropertyName(),
608                             RiceKeyConstants.ERROR_REQUIRED, inputField.getLabel());
609                 }
610 
611                 validateSearchParameterWildcardAndOperators(inputField, searchPropertyValue);
612             }
613         }
614         if (GlobalVariables.getMessageMap().hasErrors()) {
615             valid = false;
616         }
617 
618         return valid;
619     }
620 
621     /**
622      * Returns the criteria fields in a map keyed by the field property name.
623      *
624      * @param lookupView
625      * @param form lookup form instance containing the lookup data
626      * @return map of criteria fields
627      */
628     protected Map<String, InputField> getCriteriaFieldsForValidation(LookupView lookupView, LookupForm form) {
629         Map<String, InputField> criteriaFieldMap = new HashMap<String, InputField>();
630 
631         if (lookupView.getCriteriaFields() == null) {
632             return criteriaFieldMap;
633         }
634 
635         // TODO; need hooks for code generated components and also this doesn't have lifecycle which
636         // could change fields
637         List<InputField> fields = ComponentUtils.getComponentsOfTypeDeep(lookupView.getCriteriaFields(),
638                 InputField.class);
639         for (InputField field : fields) {
640             criteriaFieldMap.put(field.getPropertyName(), field);
641         }
642 
643         return criteriaFieldMap;
644     }
645 
646     /**
647      * Validates that any wildcards contained within the search value are valid wilcards and allowed for the
648      * property type for which the field is searching
649      *
650      * @param inputField - attribute field instance for the field that is being searched
651      * @param searchPropertyValue - value given for field to search for
652      */
653     protected void validateSearchParameterWildcardAndOperators(InputField inputField, String searchPropertyValue) {
654         if (StringUtils.isBlank(searchPropertyValue)) {
655             return;
656         }
657 
658         // make sure a wildcard/operator is in the value
659         boolean found = false;
660         for (SearchOperator op : SearchOperator.QUERY_CHARACTERS) {
661             String queryCharacter = op.op();
662 
663             if (searchPropertyValue.contains(queryCharacter)) {
664                 found = true;
665             }
666         }
667 
668         if (!found) {
669             return;
670         }
671 
672         String attributeLabel = inputField.getLabel();
673         if ((LookupInputField.class.isAssignableFrom(inputField.getClass())) && (((LookupInputField) inputField)
674                 .isDisableWildcardsAndOperators())) {
675             Object dataObjectExample = null;
676             try {
677                 dataObjectExample = getDataObjectClass().newInstance();
678             } catch (Exception e) {
679                 LOG.error("Exception caught instantiating " + getDataObjectClass().getName(), e);
680                 throw new RuntimeException("Cannot instantiate " + getDataObjectClass().getName(), e);
681             }
682 
683             Class<?> propertyType = ObjectPropertyUtils.getPropertyType(getDataObjectClass(),
684                     inputField.getPropertyName());
685             if (TypeUtils.isIntegralClass(propertyType) || TypeUtils.isDecimalClass(propertyType) ||
686                     TypeUtils.isTemporalClass(propertyType)) {
687                 GlobalVariables.getMessageMap().putError(inputField.getPropertyName(),
688                         RiceKeyConstants.ERROR_WILDCARDS_AND_OPERATORS_NOT_ALLOWED_ON_FIELD, attributeLabel);
689             }
690 
691             if (TypeUtils.isStringClass(propertyType)) {
692                 GlobalVariables.getMessageMap().putInfo(inputField.getPropertyName(),
693                         RiceKeyConstants.INFO_WILDCARDS_AND_OPERATORS_TREATED_LITERALLY, attributeLabel);
694             }
695         } else {
696             if (getDataObjectAuthorizationService().attributeValueNeedsToBeEncryptedOnFormsAndLinks(
697                     getDataObjectClass(), inputField.getPropertyName())) {
698                 if (!searchPropertyValue.endsWith(EncryptionService.ENCRYPTION_POST_PREFIX)) {
699                     // encrypted values usually come from the DB, so we don't
700                     // need to filter for wildcards
701                     // wildcards are not allowed on restricted fields, because
702                     // they are typically encrypted, and wildcard searches cannot be performed without
703                     // decrypting every row, which is currently not supported by KRAD
704 
705                     GlobalVariables.getMessageMap().putError(inputField.getPropertyName(),
706                             RiceKeyConstants.ERROR_SECURE_FIELD, attributeLabel);
707                 }
708             }
709         }
710     }
711 
712     /**
713      * @see org.kuali.rice.krad.lookup.Lookupable#getReturnUrlForResults
714      */
715     public void getReturnUrlForResults(Action returnLink, Object model) {
716         LookupForm lookupForm = (LookupForm) model;
717 
718         Map<String, Object> returnLinkContext = returnLink.getContext();
719         LookupView lookupView = returnLinkContext == null ? null : (LookupView) returnLinkContext
720                 .get(UifConstants.ContextVariableNames.VIEW);
721         Object dataObject = returnLinkContext == null ? null : returnLinkContext
722                 .get(UifConstants.ContextVariableNames.LINE);
723 
724         // don't render return link if the object is null or if the row is not returnable
725         if ((dataObject == null) || (!isResultReturnable(dataObject))) {
726             returnLink.setRender(false);
727             return;
728         }
729 
730         // build return link href (href may contain single quotes)
731         String href = getReturnUrl(lookupView, lookupForm, dataObject);
732         if (StringUtils.isBlank(href)) {
733             returnLink.setRender(false);
734             return;
735         }
736 
737         // build return link label and title
738         String linkLabel = getConfigurationService().getPropertyValueAsString(
739                 KRADConstants.Lookup.TITLE_RETURN_URL_PREPENDTEXT_PROPERTY);
740         returnLink.setActionLabel(linkLabel);
741 
742         List<String> returnKeys = getReturnKeys(lookupView, lookupForm, dataObject);
743         List<String> secureReturnKeys = lookupView.getAdditionalSecurePropertyNames();
744         Map<String, String> returnKeyValues = getPropertyKeyValuesFromDataObject(returnKeys, dataObject);
745 
746         String title = LookupInquiryUtils.getLinkTitleText(linkLabel, getDataObjectClass(), returnKeyValues);
747         returnLink.setTitle(title);
748 
749         // Add the return target if it is set
750         String returnTarget = lookupView.getReturnTarget();
751         if (returnTarget != null) {
752             returnLink.setActionScript("window.open(\"" + href + "\", '" + returnTarget + "');");
753 
754             //  Add the close script if lookup is in a light box
755             if (!returnTarget.equals("_self")) {
756                 // Add the return script if the returnByScript flag is set
757                 if (lookupView.isReturnByScript()) {
758                     Properties props = getReturnUrlParameters(lookupView, lookupForm, dataObject);
759 
760                     StringBuilder script = new StringBuilder("e.preventDefault();");
761                     for (String returnField : lookupForm.getFieldConversions().values()) {
762                         if (props.containsKey(returnField)) {
763                             Object value = props.get(returnField);
764                             script = script.append(
765                                     "returnLookupResultByScript(\"" + returnField + "\", '" + value + "');");
766                         }
767                     }
768                     returnLink.setActionScript(script.append("closeLightbox();").toString());
769                 } else {
770                     // Close the light box if return target is not _self or _parent
771                     returnLink.setActionScript("e.preventDefault();closeLightbox();showLoading();" +
772                             "returnLookupResultReload(\"" + href + "\", '" + returnTarget + "');");
773                 }
774             }
775         } else {
776             // If no return target is set return in same frame
777             // This is to insure that non light box lookups return correctly
778             returnLink.setActionScript("window.open(\"" + href + "\", '_self');");
779         }
780     }
781 
782     /**
783      * Builds the URL for returning the given data object result row
784      *
785      * <p>
786      * Note return URL will only be built if a return location is specified on the <code>LookupForm</code>
787      * </p>
788      *
789      * @param lookupView - lookup view instance containing lookup configuration
790      * @param lookupForm - lookup form instance containing the data
791      * @param dataObject - data object instance for the current line and for which the return URL is being built
792      * @return String return URL or blank if URL cannot be built
793      */
794     protected String getReturnUrl(LookupView lookupView, LookupForm lookupForm, Object dataObject) {
795         Properties props = getReturnUrlParameters(lookupView, lookupForm, dataObject);
796 
797         String href = "";
798         if (StringUtils.isNotBlank(lookupForm.getReturnLocation())) {
799             href = UrlFactory.parameterizeUrl(lookupForm.getReturnLocation(), props);
800         }
801 
802         return href;
803     }
804 
805     /**
806      * Builds up a <code>Properties</code> object that will be used to provide the request parameters for the
807      * return URL link
808      *
809      * @param lookupView - lookup view instance containing lookup configuration
810      * @param lookupForm - lookup form instance containing the data
811      * @param dataObject - data object instance for the current line and for which the return URL is being built
812      * @return Properties instance containing request parameters for return URL
813      */
814     protected Properties getReturnUrlParameters(LookupView lookupView, LookupForm lookupForm, Object dataObject) {
815         Properties props = new Properties();
816         props.put(KRADConstants.DISPATCH_REQUEST_PARAMETER, KRADConstants.RETURN_METHOD_TO_CALL);
817 
818         if (StringUtils.isNotBlank(lookupForm.getReturnFormKey())) {
819             props.put(UifParameters.FORM_KEY, lookupForm.getReturnFormKey());
820         }
821 
822         props.put(KRADConstants.REFRESH_CALLER, lookupView.getId());
823         props.put(KRADConstants.REFRESH_DATA_OBJECT_CLASS, getDataObjectClass().getName());
824 
825         if (StringUtils.isNotBlank(lookupForm.getDocNum())) {
826             props.put(UifParameters.DOC_NUM, lookupForm.getDocNum());
827         }
828 
829         if (StringUtils.isNotBlank(lookupForm.getReferencesToRefresh())) {
830             props.put(KRADConstants.REFERENCES_TO_REFRESH, lookupForm.getReferencesToRefresh());
831         }
832 
833         List<String> returnKeys = getReturnKeys(lookupView, lookupForm, dataObject);
834         List<String> secureReturnKeys = lookupView.getAdditionalSecurePropertyNames();
835         Map<String, String> returnKeyValues = getPropertyKeyValuesFromDataObject(returnKeys, dataObject);
836 
837         for (String returnKey : returnKeyValues.keySet()) {
838             String returnValue = returnKeyValues.get(returnKey);
839             if (lookupForm.getFieldConversions().containsKey(returnKey)) {
840                 returnKey = lookupForm.getFieldConversions().get(returnKey);
841             }
842 
843             props.put(returnKey, returnValue);
844         }
845         // props.put(UifParameters.AJAX_REQUEST,"false");
846         return props;
847     }
848 
849     /**
850      * <p>Returns the configured return key property names or if not configured defaults to the primary keys
851      * for the data object class
852      * </p>
853      *
854      * @param lookupView - lookup view instance containing lookup configuration
855      * @param lookupForm - lookup form instance containing the data
856      * @param dataObject - data object instance
857      * @return List<String> property names which should be passed back on the return URL
858      */
859     protected List<String> getReturnKeys(LookupView lookupView, LookupForm lookupForm, Object dataObject) {
860         List<String> returnKeys;
861         if (lookupForm.getFieldConversions() != null && !lookupForm.getFieldConversions().isEmpty()) {
862             returnKeys = new ArrayList<String>(lookupForm.getFieldConversions().keySet());
863         } else {
864             returnKeys = getDataObjectMetaDataService().listPrimaryKeyFieldNames(getDataObjectClass());
865         }
866 
867         return returnKeys;
868     }
869 
870     /**
871      * @see org.kuali.rice.krad.lookup.Lookupable#getMaintenanceActionLink
872      */
873     public void getMaintenanceActionLink(Action actionLink, Object model, String maintenanceMethodToCall) {
874         LookupForm lookupForm = (LookupForm) model;
875         Map<String, Object> actionLinkContext = actionLink.getContext();
876         Object dataObject = actionLinkContext == null ? null : actionLinkContext
877                 .get(UifConstants.ContextVariableNames.LINE);
878 
879         List<String> pkNames = getDataObjectMetaDataService().listPrimaryKeyFieldNames(getDataObjectClass());
880 
881         // build maintenance link href
882         String href = getActionUrlHref(lookupForm, dataObject, maintenanceMethodToCall, pkNames);
883         if (StringUtils.isBlank(href)) {
884             actionLink.setRender(false);
885             return;
886         }
887         // TODO: need to handle returning anchor
888         actionLink.setActionScript("window.open('" + href + "', '_self');");
889 
890         // build action title
891         String prependTitleText = actionLink.getActionLabel() + " " +
892                 getDataDictionaryService().getDataDictionary().getDataObjectEntry(getDataObjectClass().getName())
893                         .getObjectLabel() + " " +
894                 getConfigurationService().getPropertyValueAsString(
895                         KRADConstants.Lookup.TITLE_ACTION_URL_PREPENDTEXT_PROPERTY);
896 
897         Map<String, String> primaryKeyValues = getPropertyKeyValuesFromDataObject(pkNames, dataObject);
898         String title = LookupInquiryUtils.getLinkTitleText(prependTitleText, getDataObjectClass(), primaryKeyValues);
899         actionLink.setTitle(title);
900         lookupForm.setAtLeastOneRowHasActions(true);
901     }
902 
903     /**
904      * Generates a URL to perform a maintenance action on the given result data object
905      *
906      * <p>
907      * Will build a URL containing keys of the data object to invoke the given maintenance action method
908      * within the maintenance controller
909      * </p>
910      *
911      * @param dataObject - data object instance for the line to build the maintenance action link for
912      * @param methodToCall - method name on the maintenance controller that should be invoked
913      * @param pkNames - list of primary key field names for the data object whose key/value pairs will be added to
914      * the maintenance link
915      * @return String URL link for the maintenance action
916      */
917     protected String getActionUrlHref(LookupForm lookupForm, Object dataObject, String methodToCall,
918                                       List<String> pkNames) {
919         LookupView lookupView = (LookupView) lookupForm.getView();
920 
921         Properties props = new Properties();
922         props.put(KRADConstants.DISPATCH_REQUEST_PARAMETER, methodToCall);
923 
924         Map<String, String> primaryKeyValues = getPropertyKeyValuesFromDataObject(pkNames, dataObject);
925         for (String primaryKey : primaryKeyValues.keySet()) {
926             String primaryKeyValue = primaryKeyValues.get(primaryKey);
927 
928             props.put(primaryKey, primaryKeyValue);
929         }
930 
931         if (StringUtils.isNotBlank(lookupForm.getReturnLocation())) {
932             props.put(KRADConstants.RETURN_LOCATION_PARAMETER, lookupForm.getReturnLocation());
933         }
934 
935         props.put(UifParameters.DATA_OBJECT_CLASS_NAME, lookupForm.getDataObjectClassName());
936         props.put(UifParameters.VIEW_TYPE_NAME, UifConstants.ViewType.MAINTENANCE.name());
937 
938         String maintenanceMapping = KRADConstants.Maintenance.REQUEST_MAPPING_MAINTENANCE;
939         if (lookupView != null && StringUtils.isNotBlank(lookupView.getMaintenanceUrlMapping())) {
940             maintenanceMapping = lookupView.getMaintenanceUrlMapping();
941         }
942 
943         return UrlFactory.parameterizeUrl(maintenanceMapping, props);
944     }
945 
946     /**
947      * Sets the value for the attribute field control to contain the field conversion values for the line
948      *
949      * @see org.kuali.rice.krad.lookup.LookupableImpl#setMultiValueLookupSelect
950      */
951     @Override
952     public void setMultiValueLookupSelect(InputField selectField, Object model) {
953         LookupForm lookupForm = (LookupForm) model;
954         Map<String, Object> selectFieldContext = selectField.getContext();
955         Object lineDataObject = selectFieldContext == null ? null : selectFieldContext
956                 .get(UifConstants.ContextVariableNames.LINE);
957         if (lineDataObject == null) {
958             throw new RuntimeException("Unable to get data object for line from component: " + selectField.getId());
959         }
960 
961         Control selectControl = ((InputField) selectField).getControl();
962         if ((selectControl != null) && (selectControl instanceof ValueConfiguredControl)) {
963             String lineIdentifier = "";
964 
965             // get value for each field conversion from line and add to lineIdentifier
966             Map<String, String> fieldConversions = lookupForm.getFieldConversions();
967             List<String> fromFieldNames = new ArrayList<String>(fieldConversions.keySet());
968             Collections.sort(fromFieldNames);
969             for (String fromFieldName : fromFieldNames) {
970                 Object fromFieldValue = ObjectPropertyUtils.getPropertyValue(lineDataObject, fromFieldName);
971                 if (fromFieldValue != null) {
972                     lineIdentifier += fromFieldValue;
973                 }
974                 lineIdentifier += ":";
975             }
976             lineIdentifier = StringUtils.removeEnd(lineIdentifier, ":");
977 
978             ((ValueConfiguredControl) selectControl).setValue(lineIdentifier);
979         }
980     }
981 
982     /**
983      * Determines if given data object has associated maintenance document that allows new or copy
984      * maintenance
985      * actions
986      *
987      * @return boolean true if the maintenance new or copy action is allowed for the data object instance, false
988      *         otherwise
989      */
990     public boolean allowsMaintenanceNewOrCopyAction() {
991         boolean allowsNewOrCopy = false;
992 
993         String maintDocTypeName = getMaintenanceDocumentTypeName();
994         if (StringUtils.isNotBlank(maintDocTypeName)) {
995             allowsNewOrCopy = getDataObjectAuthorizationService().canCreate(getDataObjectClass(),
996                     GlobalVariables.getUserSession().getPerson(), maintDocTypeName);
997         }
998 
999         return allowsNewOrCopy;
1000     }
1001 
1002     /**
1003      * Determines if given data object has associated maintenance document that allows edit maintenance
1004      * actions
1005      *
1006      * @return boolean true if the maintenance edit action is allowed for the data object instance, false otherwise
1007      */
1008     public boolean allowsMaintenanceEditAction(Object dataObject) {
1009         boolean allowsEdit = false;
1010 
1011         String maintDocTypeName = getMaintenanceDocumentTypeName();
1012         if (StringUtils.isNotBlank(maintDocTypeName)) {
1013             allowsEdit = getDataObjectAuthorizationService().canMaintain(dataObject,
1014                     GlobalVariables.getUserSession().getPerson(), maintDocTypeName);
1015         }
1016 
1017         return allowsEdit;
1018     }
1019 
1020     /**
1021      * Determines if given data object has associated maintenance document that allows delete maintenance
1022      * actions.
1023      *
1024      * @return boolean true if the maintenance delete action is allowed for the data object instance, false otherwise
1025      */
1026     public boolean allowsMaintenanceDeleteAction(Object dataObject) {
1027         boolean allowsMaintain = false;
1028         boolean allowsDelete = false;
1029 
1030         String maintDocTypeName = getMaintenanceDocumentTypeName();
1031         if (StringUtils.isNotBlank(maintDocTypeName)) {
1032             allowsMaintain = getDataObjectAuthorizationService().canMaintain(dataObject,
1033                     GlobalVariables.getUserSession().getPerson(), maintDocTypeName);
1034         }
1035 
1036         allowsDelete = getDocumentDictionaryService().getAllowsRecordDeletion(getDataObjectClass());
1037 
1038         return allowsDelete && allowsMaintain;
1039     }
1040 
1041     /**
1042      * Returns the maintenance document type associated with the business object class or null if one does not exist.
1043      *
1044      * @return String representing the maintenance document type name
1045      */
1046     protected String getMaintenanceDocumentTypeName() {
1047         DocumentDictionaryService dd = getDocumentDictionaryService();
1048         String maintDocTypeName = dd.getMaintenanceDocumentTypeName(getDataObjectClass());
1049 
1050         return maintDocTypeName;
1051     }
1052 
1053     /**
1054      * Determines whether a given data object that's returned as one of the lookup's results is considered returnable,
1055      * which means that for single-value lookups, a "return value" link may be rendered, and for multiple
1056      * value lookups, a checkbox is rendered.
1057      *
1058      * Note that this can be part of an authorization mechanism, but not the complete authorization mechanism.  The
1059      * component that invoked the lookup/ lookup caller (e.g. document, nesting lookup, etc.) needs to check
1060      * that the object that was passed to it was returnable as well because there are ways around this method
1061      * (e.g. crafting a custom return URL).
1062      *
1063      * @param dataObject - an object from the search result set
1064      * @return true if the row is returnable and false if it is not
1065      */
1066     protected boolean isResultReturnable(Object dataObject) {
1067         return true;
1068     }
1069 
1070     /**
1071      * @see org.kuali.rice.krad.lookup.Lookupable#setDataObjectClass
1072      */
1073     @Override
1074     public void setDataObjectClass(Class<?> dataObjectClass) {
1075         this.dataObjectClass = dataObjectClass;
1076     }
1077 
1078     /**
1079      * @see org.kuali.rice.krad.lookup.Lookupable#getDataObjectClass
1080      */
1081     @Override
1082     public Class<?> getDataObjectClass() {
1083         return this.dataObjectClass;
1084     }
1085 
1086     public void setConfigurationService(ConfigurationService configurationService) {
1087         this.configurationService = configurationService;
1088     }
1089 
1090     protected DataObjectAuthorizationService getDataObjectAuthorizationService() {
1091         if (dataObjectAuthorizationService == null) {
1092             this.dataObjectAuthorizationService = KRADServiceLocatorWeb.getDataObjectAuthorizationService();
1093         }
1094         return dataObjectAuthorizationService;
1095     }
1096 
1097     public void setDataObjectAuthorizationService(DataObjectAuthorizationService dataObjectAuthorizationService) {
1098         this.dataObjectAuthorizationService = dataObjectAuthorizationService;
1099     }
1100 
1101     protected DataObjectMetaDataService getDataObjectMetaDataService() {
1102         if (dataObjectMetaDataService == null) {
1103             this.dataObjectMetaDataService = KRADServiceLocatorWeb.getDataObjectMetaDataService();
1104         }
1105         return dataObjectMetaDataService;
1106     }
1107 
1108     public void setDataObjectMetaDataService(DataObjectMetaDataService dataObjectMetaDataService) {
1109         this.dataObjectMetaDataService = dataObjectMetaDataService;
1110     }
1111 
1112     public DocumentDictionaryService getDocumentDictionaryService() {
1113         if (documentDictionaryService == null) {
1114             documentDictionaryService = KRADServiceLocatorWeb.getDocumentDictionaryService();
1115         }
1116         return documentDictionaryService;
1117     }
1118 
1119     public void setDocumentDictionaryService(DocumentDictionaryService documentDictionaryService) {
1120         this.documentDictionaryService = documentDictionaryService;
1121     }
1122 
1123     protected LookupService getLookupService() {
1124         if (lookupService == null) {
1125             this.lookupService = KRADServiceLocatorWeb.getLookupService();
1126         }
1127         return lookupService;
1128     }
1129 
1130     public void setLookupService(LookupService lookupService) {
1131         this.lookupService = lookupService;
1132     }
1133 
1134     protected EncryptionService getEncryptionService() {
1135         if (encryptionService == null) {
1136             this.encryptionService = CoreApiServiceLocator.getEncryptionService();
1137         }
1138         return encryptionService;
1139     }
1140 
1141     public void setEncryptionService(EncryptionService encryptionService) {
1142         this.encryptionService = encryptionService;
1143     }
1144 
1145     //TODO: This code snippet is from KRADUtils of Rice 2.3.2
1146     public Map<String, String> getPropertyKeyValuesFromDataObject(List<String> propertyNames, Object dataObject) {
1147         Map<String, String> propertyKeyValues = new HashMap<String, String>();
1148 
1149         if (dataObject == null) {
1150             return propertyKeyValues;
1151         }
1152 
1153         // iterate through properties and add a map entry for each
1154         for (String propertyName : propertyNames) {
1155             Object propertyValue = ObjectPropertyUtils.getPropertyValue(dataObject, propertyName);
1156             if (propertyValue == null) {
1157                 propertyValue = StringUtils.EMPTY;
1158             }
1159 
1160             if (KRADServiceLocatorWeb.getDataObjectAuthorizationService()
1161                     .attributeValueNeedsToBeEncryptedOnFormsAndLinks(dataObject.getClass(), propertyName)) {
1162                 try {
1163                     if (CoreApiServiceLocator.getEncryptionService().isEnabled()) {
1164                         propertyValue = CoreApiServiceLocator.getEncryptionService().encrypt(propertyValue)
1165                                 + EncryptionService.ENCRYPTION_POST_PREFIX;
1166                     }
1167                 } catch (GeneralSecurityException e) {
1168                     throw new RuntimeException("Exception while trying to encrypt value for key/value map.", e);
1169                 }
1170             }
1171 
1172             // TODO: need to apply formatting to return value once util class is ready
1173             propertyKeyValues.put(propertyName, propertyValue.toString());
1174         }
1175 
1176         return propertyKeyValues;
1177     }
1178 }