View Javadoc

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