View Javadoc

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