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