Coverage Report - org.kuali.rice.krad.lookup.LookupableImpl
 
Classes in this File Line Coverage Branch Coverage Complexity
LookupableImpl
0%
0/321
0%
0/150
3.065
 
 1  
 /*
 2  
  * Copyright 2007 The Kuali Foundation
 3  
  *
 4  
  * Licensed under the Educational Community License, Version 1.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/ecl1.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.util.RiceKeyConstants;
 26  
 import org.kuali.rice.core.util.type.TypeUtils;
 27  
 import org.kuali.rice.kim.bo.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.KRADServiceLocator;
 35  
 import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
 36  
 import org.kuali.rice.krad.service.LookupService;
 37  
 import org.kuali.rice.krad.service.ModuleService;
 38  
 import org.kuali.rice.krad.uif.UifConstants;
 39  
 import org.kuali.rice.krad.uif.UifParameters;
 40  
 import org.kuali.rice.krad.uif.container.LookupView;
 41  
 import org.kuali.rice.krad.uif.container.View;
 42  
 import org.kuali.rice.krad.uif.control.HiddenControl;
 43  
 import org.kuali.rice.krad.uif.field.AttributeField;
 44  
 import org.kuali.rice.krad.uif.field.GeneratedField;
 45  
 import org.kuali.rice.krad.uif.field.LinkField;
 46  
 import org.kuali.rice.krad.uif.field.LookupCriteriaAttributeField;
 47  
 import org.kuali.rice.krad.uif.service.impl.ViewHelperServiceImpl;
 48  
 import org.kuali.rice.krad.uif.util.LookupInquiryUtils;
 49  
 import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
 50  
 import org.kuali.rice.krad.util.BeanPropertyComparator;
 51  
 import org.kuali.rice.krad.util.GlobalVariables;
 52  
 import org.kuali.rice.krad.util.KRADConstants;
 53  
 import org.kuali.rice.krad.util.KRADUtils;
 54  
 import org.kuali.rice.krad.util.ObjectUtils;
 55  
 import org.kuali.rice.krad.util.UrlFactory;
 56  
 import org.kuali.rice.krad.web.controller.MaintenanceDocumentController;
 57  
 import org.kuali.rice.krad.web.form.LookupForm;
 58  
 
 59  
 import java.security.GeneralSecurityException;
 60  
 import java.util.ArrayList;
 61  
 import java.util.Collection;
 62  
 import java.util.Collections;
 63  
 import java.util.HashMap;
 64  
 import java.util.List;
 65  
 import java.util.Map;
 66  
 import java.util.Properties;
 67  
 
 68  
 /**
 69  
  * Default implementation of <code>Lookupable</code>
 70  
  *
 71  
  * @author Kuali Rice Team (rice.collab@kuali.org)
 72  
  */
 73  0
 public class LookupableImpl extends ViewHelperServiceImpl implements Lookupable {
 74  0
     private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(LookupableImpl.class);
 75  
 
 76  
     private Class<?> dataObjectClass;
 77  
 
 78  
     private Map<String, String> parameters;
 79  
     private List<String> defaultSortAttributeNames;
 80  
 
 81  
     // TODO delyea: where to take into account the sort ascending value (old KNS appeared to ignore?)
 82  
     private boolean sortAscending;
 83  
 
 84  
     private Map<String, String> fieldConversions;
 85  
     private List<String> readOnlyFieldsList;
 86  
 
 87  
     private transient ConfigurationService configurationService;
 88  
     private transient DataObjectAuthorizationService dataObjectAuthorizationService;
 89  
     private transient DataObjectMetaDataService dataObjectMetaDataService;
 90  
     private transient DocumentDictionaryService documentDictionaryService;
 91  
     private transient LookupService lookupService;
 92  
     private transient EncryptionService encryptionService;
 93  
 
 94  
     /**
 95  
      * @see org.kuali.rice.krad.uif.service.impl.ViewHelperServiceImpl#populateViewFromRequestParameters(org.kuali.rice.krad.uif.container.View,
 96  
      *      java.util.Map)
 97  
      */
 98  
     @Override
 99  
     public void populateViewFromRequestParameters(View view, Map<String, String> parameters) {
 100  0
         super.populateViewFromRequestParameters(view, parameters);
 101  
         /* On the old Lookupable and LookupableHelperService in KNS the parameters list used to have multipart form
 102  
            * data in it where it may not in the new KRAD. See PojoFormBase.populate() method for more information
 103  
            */
 104  0
         setParameters(parameters);
 105  0
     }
 106  
 
 107  
     /**
 108  
      * Initialization of Lookupable requires that the business object class be set for the {@link
 109  
      * #initializeAttributeFieldFromDataDictionary(View, org.kuali.rice.krad.uif.field.AttributeField)} method
 110  
      *
 111  
      * @see org.kuali.rice.krad.uif.service.impl.ViewHelperServiceImpl#performInitialization(org.kuali.rice.krad.uif.container.View)
 112  
      */
 113  
     @Override
 114  
     public void performInitialization(View view) {
 115  0
         if (!LookupView.class.isAssignableFrom(view.getClass())) {
 116  0
             throw new IllegalArgumentException(
 117  
                     "View class '" + view.getClass() + " is not assignable from the '" + LookupView.class + "'");
 118  
         }
 119  
 
 120  0
         LookupView lookupView = (LookupView) view;
 121  0
         initializeLookupViewHelperService(lookupView);
 122  
 
 123  0
         super.performInitialization(view);
 124  0
     }
 125  
 
 126  
     /**
 127  
      * Initializes properties on this lookupable from the <code>LookupView</code>
 128  
      *
 129  
      * @param lookupView - lookup view instance
 130  
      */
 131  
     protected void initializeLookupViewHelperService(LookupView lookupView) {
 132  0
         setDefaultSortAttributeNames(lookupView.getDefaultSortAttributeNames());
 133  0
         setSortAscending(lookupView.isSortAscending());
 134  0
         setDataObjectClass(lookupView.getDataObjectClassName());
 135  0
     }
 136  
 
 137  
     /**
 138  
      * @see org.kuali.rice.krad.lookup.Lookupable#performSearch
 139  
      */
 140  
     @Override
 141  
     public Collection<?> performSearch(LookupForm form, Map<String, String> searchCriteria, boolean bounded) {
 142  
         Collection<?> displayList;
 143  
 
 144  0
         LookupUtils.preprocessDateFields(searchCriteria);
 145  
 
 146  
         // TODO: force uppercase will be done in binding at some point
 147  0
         displayList =
 148  
                 getSearchResults(form, LookupUtils.forceUppercase(getDataObjectClass(), searchCriteria), !bounded);
 149  
 
 150  
         // TODO delyea - is this the best way to set that the entire set has a returnable row?
 151  0
         List<String> pkNames = getDataObjectMetaDataService().listPrimaryKeyFieldNames(getDataObjectClass());
 152  0
         Person user = GlobalVariables.getUserSession().getPerson();
 153  
 
 154  0
         for (Object object : displayList) {
 155  0
             if (isResultReturnable(object)) {
 156  0
                 form.setAtLeastOneRowReturnable(true);
 157  
             }
 158  
         }
 159  
 
 160  0
         return displayList;
 161  
     }
 162  
 
 163  
     protected List<?> getSearchResults(LookupForm form, Map<String, String> searchCriteria, boolean unbounded) {
 164  
         List<?> searchResults;
 165  
 
 166  
         // removed blank search values and decrypt any encrypted search values
 167  0
         Map<String, String> nonBlankSearchCriteria = processSearchCriteria(form, searchCriteria);
 168  
 
 169  0
         boolean searchUsingOnlyPrimaryKeyValues =
 170  
                 getLookupService().allPrimaryKeyValuesPresentAndNotWildcard(getDataObjectClass(), searchCriteria);
 171  
 
 172  
         // if this class is an EBO, just call the module service to get the results
 173  0
         if (ExternalizableBusinessObject.class.isAssignableFrom(getDataObjectClass())) {
 174  0
             return getSearchResultsForEBO(nonBlankSearchCriteria, unbounded);
 175  
         }
 176  
 
 177  
         // if any of the properties refer to an embedded EBO, call the EBO
 178  
         // lookups first and apply to the local lookup
 179  
         try {
 180  0
             if (LookupUtils.hasExternalBusinessObjectProperty(getDataObjectClass(), nonBlankSearchCriteria)) {
 181  0
                 Map<String, String> eboSearchCriteria = adjustCriteriaForNestedEBOs(nonBlankSearchCriteria, unbounded);
 182  
 
 183  0
                 if (LOG.isDebugEnabled()) {
 184  0
                     LOG.debug("Passing these results into the lookup service: " + eboSearchCriteria);
 185  
                 }
 186  
 
 187  
                 // add those results as criteria run the normal search (but with the EBO criteria added)
 188  0
                 searchResults = (List<?>) getLookupService()
 189  
                         .findCollectionBySearchHelper(getDataObjectClass(), eboSearchCriteria, unbounded);
 190  0
             } else {
 191  0
                 searchResults = (List<?>) getLookupService()
 192  
                         .findCollectionBySearchHelper(getDataObjectClass(), nonBlankSearchCriteria, unbounded);
 193  
             }
 194  0
         } catch (IllegalAccessException e) {
 195  0
             LOG.error("Error trying to perform search", e);
 196  0
             throw new RuntimeException("Error trying to perform search", e);
 197  0
         } catch (InstantiationException e1) {
 198  0
             LOG.error("Error trying to perform search", e1);
 199  0
             throw new RuntimeException("Error trying to perform search", e1);
 200  0
         }
 201  
 
 202  0
         if (searchResults == null) {
 203  0
             searchResults = new ArrayList<Object>();
 204  
         }
 205  
 
 206  
         // sort list if default sort column given
 207  0
         List<String> defaultSortColumns = getDefaultSortAttributeNames();
 208  0
         if ((defaultSortColumns != null) && (defaultSortColumns.size() > 0)) {
 209  0
             Collections.sort(searchResults, new BeanPropertyComparator(defaultSortColumns, true));
 210  
         }
 211  
 
 212  0
         return searchResults;
 213  
     }
 214  
 
 215  
     protected Map<String, String> processSearchCriteria(LookupForm lookupForm, Map<String, String> searchCriteria) {
 216  0
         Map<String, String> nonBlankSearchCriteria = new HashMap<String, String>();
 217  0
         for (String fieldName : searchCriteria.keySet()) {
 218  0
             String fieldValue = searchCriteria.get(fieldName);
 219  
 
 220  
             // don't add hidden criteria
 221  0
             LookupView lookupView = (LookupView) lookupForm.getView();
 222  0
             AttributeField attributeField = lookupView.getViewIndex().getAttributeFieldByPropertyName(fieldName);
 223  0
             if (attributeField.getControl() instanceof HiddenControl) {
 224  0
                 continue;
 225  
             }
 226  
 
 227  
             // only add criteria if non blank
 228  0
             if (StringUtils.isNotBlank(fieldValue)) {
 229  0
                 if (fieldValue.endsWith(EncryptionService.ENCRYPTION_POST_PREFIX)) {
 230  0
                     String encryptedValue = StringUtils.removeEnd(fieldValue, EncryptionService.ENCRYPTION_POST_PREFIX);
 231  
                     try {
 232  0
                         fieldValue = getEncryptionService().decrypt(encryptedValue);
 233  0
                     } catch (GeneralSecurityException e) {
 234  0
                         LOG.error("Error decrypting value for business object class " + getDataObjectClass() +
 235  
                                 " attribute " + fieldName, e);
 236  0
                         throw new RuntimeException(
 237  
                                 "Error decrypting value for business object class " + getDataObjectClass() +
 238  
                                         " attribute " + fieldName, e);
 239  0
                     }
 240  
                 }
 241  
 
 242  0
                 nonBlankSearchCriteria.put(fieldName, fieldValue);
 243  
             }
 244  0
         }
 245  
 
 246  0
         return nonBlankSearchCriteria;
 247  
     }
 248  
 
 249  
     protected List<?> getSearchResultsForEBO(Map<String, String> searchCriteria, boolean unbounded) {
 250  0
         ModuleService eboModuleService =
 251  
                 KRADServiceLocatorWeb.getKualiModuleService().getResponsibleModuleService(getDataObjectClass());
 252  0
         BusinessObjectEntry ddEntry =
 253  
                 eboModuleService.getExternalizableBusinessObjectDictionaryEntry(getDataObjectClass());
 254  
 
 255  0
         Map<String, String> filteredFieldValues = new HashMap<String, String>();
 256  0
         for (String fieldName : searchCriteria.keySet()) {
 257  0
             if (ddEntry.getAttributeNames().contains(fieldName)) {
 258  0
                 filteredFieldValues.put(fieldName, searchCriteria.get(fieldName));
 259  
             }
 260  
         }
 261  
 
 262  0
         List<?> searchResults = eboModuleService.getExternalizableBusinessObjectsListForLookup(
 263  
                 (Class<? extends ExternalizableBusinessObject>) getDataObjectClass(), (Map) filteredFieldValues,
 264  
                 unbounded);
 265  
 
 266  0
         return searchResults;
 267  
     }
 268  
 
 269  
     protected Map<String, String> adjustCriteriaForNestedEBOs(Map<String, String> searchCriteria,
 270  
             boolean unbounded) throws InstantiationException, IllegalAccessException {
 271  0
         if (LOG.isDebugEnabled()) {
 272  0
             LOG.debug("has EBO reference: " + getDataObjectClass());
 273  0
             LOG.debug("properties: " + searchCriteria);
 274  
         }
 275  
 
 276  
         // remove the EBO criteria
 277  0
         Map<String, String> nonEboFieldValues =
 278  
                 LookupUtils.removeExternalizableBusinessObjectFieldValues(getDataObjectClass(), searchCriteria);
 279  0
         if (LOG.isDebugEnabled()) {
 280  0
             LOG.debug("Non EBO properties removed: " + nonEboFieldValues);
 281  
         }
 282  
 
 283  
         // get the list of EBO properties attached to this object
 284  0
         List<String> eboPropertyNames =
 285  
                 LookupUtils.getExternalizableBusinessObjectProperties(getDataObjectClass(), searchCriteria);
 286  0
         if (LOG.isDebugEnabled()) {
 287  0
             LOG.debug("EBO properties: " + eboPropertyNames);
 288  
         }
 289  
 
 290  
         // loop over those properties
 291  0
         for (String eboPropertyName : eboPropertyNames) {
 292  
             // extract the properties as known to the EBO
 293  0
             Map<String, String> eboFieldValues =
 294  
                     LookupUtils.getExternalizableBusinessObjectFieldValues(eboPropertyName, searchCriteria);
 295  0
             if (LOG.isDebugEnabled()) {
 296  0
                 LOG.debug("EBO properties for master EBO property: " + eboPropertyName);
 297  0
                 LOG.debug("properties: " + eboFieldValues);
 298  
             }
 299  
 
 300  
             // run search against attached EBO's module service
 301  0
             ModuleService eboModuleService = KRADServiceLocatorWeb.getKualiModuleService().getResponsibleModuleService(
 302  
                     LookupUtils.getExternalizableBusinessObjectClass(getDataObjectClass(), eboPropertyName));
 303  
 
 304  
             // KULRICE-4401 made eboResults an empty list and only filled if
 305  
             // service is found.
 306  0
             List<?> eboResults = Collections.emptyList();
 307  0
             if (eboModuleService != null) {
 308  0
                 eboResults = eboModuleService.getExternalizableBusinessObjectsListForLookup(
 309  
                         LookupUtils.getExternalizableBusinessObjectClass(getDataObjectClass(), eboPropertyName),
 310  
                         (Map) eboFieldValues, unbounded);
 311  
             } else {
 312  0
                 LOG.debug("EBO ModuleService is null: " + eboPropertyName);
 313  
             }
 314  
             // get the mapping/relationship between the EBO object and it's
 315  
             // parent object
 316  
             // use that to adjust the searchCriteria
 317  
 
 318  
             // get the parent property type
 319  
             Class<?> eboParentClass;
 320  
             String eboParentPropertyName;
 321  0
             if (ObjectUtils.isNestedAttribute(eboPropertyName)) {
 322  0
                 eboParentPropertyName = StringUtils.substringBeforeLast(eboPropertyName, ".");
 323  
                 try {
 324  0
                     eboParentClass =
 325  
                             PropertyUtils.getPropertyType(getDataObjectClass().newInstance(), eboParentPropertyName);
 326  0
                 } catch (Exception ex) {
 327  0
                     throw new RuntimeException("Unable to create an instance of the business object class: " +
 328  
                             getDataObjectClass().getName(), ex);
 329  0
                 }
 330  
             } else {
 331  0
                 eboParentClass = getDataObjectClass();
 332  0
                 eboParentPropertyName = null;
 333  
             }
 334  
 
 335  0
             if (LOG.isDebugEnabled()) {
 336  0
                 LOG.debug("determined EBO parent class/property name: " + eboParentClass + "/" + eboParentPropertyName);
 337  
             }
 338  
 
 339  
             // look that up in the DD (BOMDS)
 340  
             // find the appropriate relationship
 341  
             // CHECK THIS: what if eboPropertyName is a nested attribute -
 342  
             // need to strip off the eboParentPropertyName if not null
 343  0
             RelationshipDefinition rd =
 344  
                     getDataObjectMetaDataService().getDictionaryRelationship(eboParentClass, eboPropertyName);
 345  0
             if (LOG.isDebugEnabled()) {
 346  0
                 LOG.debug("Obtained RelationshipDefinition for " + eboPropertyName);
 347  0
                 LOG.debug(rd);
 348  
             }
 349  
 
 350  
             // copy the needed properties (primary only) to the field values KULRICE-4446 do
 351  
             // so only if the relationship definition exists
 352  
             // NOTE: this will work only for single-field PK unless the ORM
 353  
             // layer is directly involved
 354  
             // (can't make (field1,field2) in ( (v1,v2),(v3,v4) ) style
 355  
             // queries in the lookup framework
 356  0
             if (ObjectUtils.isNotNull(rd)) {
 357  0
                 if (rd.getPrimitiveAttributes().size() > 1) {
 358  0
                     throw new RuntimeException(
 359  
                             "EBO Links don't work for relationships with multiple-field primary keys.");
 360  
                 }
 361  0
                 String boProperty = rd.getPrimitiveAttributes().get(0).getSourceName();
 362  0
                 String eboProperty = rd.getPrimitiveAttributes().get(0).getTargetName();
 363  0
                 StringBuffer boPropertyValue = new StringBuffer();
 364  
 
 365  
                 // loop over the results, making a string that the lookup
 366  
                 // DAO will convert into an
 367  
                 // SQL "IN" clause
 368  0
                 for (Object ebo : eboResults) {
 369  0
                     if (boPropertyValue.length() != 0) {
 370  0
                         boPropertyValue.append(SearchOperator.OR.op());
 371  
                     }
 372  
                     try {
 373  0
                         boPropertyValue.append(PropertyUtils.getProperty(ebo, eboProperty).toString());
 374  0
                     } catch (Exception ex) {
 375  0
                         LOG.warn("Unable to get value for " + eboProperty + " on " + ebo);
 376  0
                     }
 377  
                 }
 378  
 
 379  0
                 if (eboParentPropertyName == null) {
 380  
                     // non-nested property containing the EBO
 381  0
                     nonEboFieldValues.put(boProperty, boPropertyValue.toString());
 382  
                 } else {
 383  
                     // property nested within the main searched-for BO that
 384  
                     // contains the EBO
 385  0
                     nonEboFieldValues.put(eboParentPropertyName + "." + boProperty, boPropertyValue.toString());
 386  
                 }
 387  
             }
 388  0
         }
 389  
 
 390  0
         return nonEboFieldValues;
 391  
     }
 392  
 
 393  
     /**
 394  
      * @see org.kuali.rice.krad.lookup.Lookupable#performClear
 395  
      */
 396  
     @Override
 397  
     public Map<String, String> performClear(LookupForm form, Map<String, String> searchCriteria) {
 398  0
         Map<String, String> clearedSearchCriteria = new HashMap<String, String>();
 399  0
         for (Map.Entry<String, String> searchKeyValue : searchCriteria.entrySet()) {
 400  0
             String searchPropertyName = searchKeyValue.getKey();
 401  0
             String searchPropertyValue = searchKeyValue.getValue();
 402  
 
 403  0
             LookupView lookupView = (LookupView) form.getView();
 404  0
             AttributeField attributeField =
 405  
                     lookupView.getViewIndex().getAttributeFieldByPropertyName(searchPropertyName);
 406  0
             if (attributeField != null) {
 407  
                 // TODO: check secure fields
 408  
 //                                if (field.isSecure()) {
 409  
 //                    field.setSecure(false);
 410  
 //                    field.setDisplayMaskValue(null);
 411  
 //                    field.setEncryptedValue(null);
 412  
 //                }
 413  
 
 414  
                 // TODO: need formatting on default value and make sure it works when control converts
 415  
                 // from checkbox to radio
 416  0
                 clearedSearchCriteria.put(searchPropertyName, attributeField.getDefaultValue());
 417  
             } else {
 418  0
                 throw new RuntimeException("Invalid search field sent for property name: " + searchPropertyName);
 419  
             }
 420  0
         }
 421  
 
 422  0
         return clearedSearchCriteria;
 423  
     }
 424  
 
 425  
     /**
 426  
      * @see org.kuali.rice.krad.lookup.Lookupable#validateSearchParameters
 427  
      */
 428  
     @Override
 429  
     public boolean validateSearchParameters(LookupForm form, Map<String, String> searchCriteria) {
 430  0
         boolean valid = true;
 431  
 
 432  0
         if (!getViewDictionaryService().isLookupable(getDataObjectClass())) {
 433  0
             throw new RuntimeException("Lookup not defined for data object " + getDataObjectClass());
 434  
         }
 435  
 
 436  
         // validate required
 437  
         // TODO: this will be done by the uif validation service at some point
 438  0
         for (Map.Entry<String, String> searchKeyValue : searchCriteria.entrySet()) {
 439  0
             String searchPropertyName = searchKeyValue.getKey();
 440  0
             String searchPropertyValue = searchKeyValue.getValue();
 441  
 
 442  0
             LookupView lookupView = (LookupView) form.getView();
 443  0
             AttributeField attributeField =
 444  
                     lookupView.getViewIndex().getAttributeFieldByPropertyName(searchPropertyName);
 445  0
             if (attributeField != null) {
 446  0
                 if (StringUtils.isBlank(searchPropertyValue) && BooleanUtils.isTrue(attributeField.getRequired())) {
 447  0
                     GlobalVariables.getMessageMap()
 448  
                             .putError(attributeField.getPropertyName(), RiceKeyConstants.ERROR_REQUIRED,
 449  
                                     attributeField.getLabel());
 450  
                 }
 451  
 
 452  0
                 validateSearchParameterWildcardAndOperators(attributeField, searchPropertyValue);
 453  
             } else {
 454  0
                 throw new RuntimeException("Invalid search field sent for property name: " + searchPropertyName);
 455  
             }
 456  0
         }
 457  
 
 458  0
         if (GlobalVariables.getMessageMap().hasErrors()) {
 459  0
             valid = false;
 460  
         }
 461  
 
 462  0
         return valid;
 463  
     }
 464  
 
 465  
     /**
 466  
      * Validates that any wildcards contained within the search value are valid wilcards and allowed for the
 467  
      * property type for which the field is searching
 468  
      *
 469  
      * @param attributeField - attribute field instance for the field that is being searched
 470  
      * @param searchPropertyValue - value given for field to search for
 471  
      */
 472  
     protected void validateSearchParameterWildcardAndOperators(AttributeField attributeField,
 473  
             String searchPropertyValue) {
 474  0
         if (StringUtils.isBlank(searchPropertyValue))
 475  0
             return;
 476  
 
 477  
         // make sure a wildcard/operator is in the value
 478  0
         boolean found = false;
 479  0
         for (SearchOperator op : SearchOperator.QUERY_CHARACTERS) {
 480  0
             String queryCharacter = op.op();
 481  
 
 482  0
             if (searchPropertyValue.contains(queryCharacter)) {
 483  0
                 found = true;
 484  
             }
 485  0
         }
 486  
 
 487  0
         if (!found) {
 488  0
             return;
 489  
         }
 490  
 
 491  0
         String attributeLabel = attributeField.getLabel();
 492  0
         if ((LookupCriteriaAttributeField.class.isAssignableFrom(attributeField.getClass())) &&
 493  
                 (((LookupCriteriaAttributeField) attributeField).isTreatWildcardsAndOperatorsAsLiteral())) {
 494  0
             Object dataObjectExample = null;
 495  
             try {
 496  0
                 dataObjectExample = getDataObjectClass().newInstance();
 497  0
             } catch (Exception e) {
 498  0
                 LOG.error("Exception caught instantiating " + getDataObjectClass().getName(), e);
 499  0
                 throw new RuntimeException("Cannot instantiate " + getDataObjectClass().getName(), e);
 500  0
             }
 501  
 
 502  0
             Class<?> propertyType =
 503  
                     ObjectPropertyUtils.getPropertyType(getDataObjectClass(), attributeField.getPropertyName());
 504  0
             if (TypeUtils.isIntegralClass(propertyType) || TypeUtils.isDecimalClass(propertyType) ||
 505  
                     TypeUtils.isTemporalClass(propertyType)) {
 506  0
                 GlobalVariables.getMessageMap().putError(attributeField.getPropertyName(),
 507  
                         RiceKeyConstants.ERROR_WILDCARDS_AND_OPERATORS_NOT_ALLOWED_ON_FIELD, attributeLabel);
 508  
             }
 509  
 
 510  0
             if (TypeUtils.isStringClass(propertyType)) {
 511  0
                 GlobalVariables.getMessageMap().putInfo(attributeField.getPropertyName(),
 512  
                         RiceKeyConstants.INFO_WILDCARDS_AND_OPERATORS_TREATED_LITERALLY, attributeLabel);
 513  
             }
 514  0
         } else {
 515  0
             if (getDataObjectAuthorizationService()
 516  
                     .attributeValueNeedsToBeEncryptedOnFormsAndLinks(getDataObjectClass(),
 517  
                             attributeField.getPropertyName())) {
 518  0
                 if (!searchPropertyValue.endsWith(EncryptionService.ENCRYPTION_POST_PREFIX)) {
 519  
                     // encrypted values usually come from the DB, so we don't
 520  
                     // need to filter for wildcards
 521  
                     // wildcards are not allowed on restricted fields, because
 522  
                     // they are typically encrypted, and wildcard searches cannot be performed without
 523  
                     // decrypting every row, which is currently not supported by KRAD
 524  
 
 525  0
                     GlobalVariables.getMessageMap()
 526  
                             .putError(attributeField.getPropertyName(), RiceKeyConstants.ERROR_SECURE_FIELD,
 527  
                                     attributeLabel);
 528  
                 }
 529  
             }
 530  
         }
 531  0
     }
 532  
 
 533  
     /**
 534  
      * @see org.kuali.rice.krad.lookup.Lookupable#getReturnUrlForResults
 535  
      */
 536  
     public void getReturnUrlForResults(LinkField returnLinkField, Object model) {
 537  0
         LookupForm lookupForm = (LookupForm) model;
 538  0
         LookupView lookupView = (LookupView) returnLinkField.getContext().get(UifConstants.ContextVariableNames.VIEW);
 539  
 
 540  0
         Object dataObject = returnLinkField.getContext().get(UifConstants.ContextVariableNames.LINE);
 541  
 
 542  
         // return a non-breaking space if the object is null or if the row is not returnable
 543  0
         if ((dataObject == null) || (!isResultReturnable(dataObject))) {
 544  0
             returnLinkField.setRender(false);
 545  0
             return;
 546  
         }
 547  
 
 548  
         // build return link href
 549  0
         String href = getReturnUrl(lookupView, lookupForm, dataObject);
 550  0
         if (StringUtils.isBlank(href)) {
 551  0
             returnLinkField.setRender(false);
 552  0
             return;
 553  
         }
 554  
         // TODO: need to handle returning anchor
 555  0
         returnLinkField.setHrefText(href);
 556  
 
 557  
         // build return link label and title
 558  0
         String linkLabel =
 559  
                 getConfigurationService().getPropertyString(KRADConstants.Lookup.TITLE_RETURN_URL_PREPENDTEXT_PROPERTY);
 560  0
         returnLinkField.setLinkLabel(linkLabel);
 561  
 
 562  0
         List<String> returnKeys = getReturnKeys(lookupView, lookupForm, dataObject);
 563  0
         Map<String, String> returnKeyValues = KRADUtils.getPropertyKeyValuesFromDataObject(returnKeys, dataObject);
 564  
 
 565  0
         String title = LookupInquiryUtils.getLinkTitleText(linkLabel, getDataObjectClass(), returnKeyValues);
 566  0
         returnLinkField.setTitle(title);
 567  0
     }
 568  
 
 569  
     /**
 570  
      * Builds the URL for returning the given data object result row
 571  
      *
 572  
      * <p>
 573  
      * Note return URL will only be built if a return location is specified on the <code>LookupForm</code>
 574  
      * </p>
 575  
      *
 576  
      * @param lookupView - lookup view instance containing lookup configuration
 577  
      * @param lookupForm - lookup form instance containing the data
 578  
      * @param dataObject - data object instance for the current line and for which the return URL is being built
 579  
      * @return String return URL or blank if URL cannot be built
 580  
      */
 581  
     protected String getReturnUrl(LookupView lookupView, LookupForm lookupForm, Object dataObject) {
 582  0
         Properties props = getReturnUrlParameters(lookupView, lookupForm, dataObject);
 583  
 
 584  0
         String href = "";
 585  0
         if (StringUtils.isNotBlank(lookupForm.getReturnLocation())) {
 586  0
             href = UrlFactory.parameterizeUrl(lookupForm.getReturnLocation(), props);
 587  
         }
 588  
 
 589  0
         return href;
 590  
     }
 591  
 
 592  
     /**
 593  
      * Builds up a <code>Properties</code> object that will be used to provide the request parameters for the
 594  
      * return URL link
 595  
      *
 596  
      * @param lookupView - lookup view instance containing lookup configuration
 597  
      * @param lookupForm - lookup form instance containing the data
 598  
      * @param dataObject - data object instance for the current line and for which the return URL is being built
 599  
      * @return Properties instance containing request parameters for return URL
 600  
      */
 601  
     protected Properties getReturnUrlParameters(LookupView lookupView, LookupForm lookupForm, Object dataObject) {
 602  0
         Properties props = new Properties();
 603  0
         props.put(KRADConstants.DISPATCH_REQUEST_PARAMETER, KRADConstants.RETURN_METHOD_TO_CALL);
 604  
 
 605  0
         if (StringUtils.isNotBlank(lookupForm.getReturnFormKey())) {
 606  0
             props.put(UifParameters.FORM_KEY, lookupForm.getReturnFormKey());
 607  
         }
 608  
 
 609  0
         props.put(KRADConstants.REFRESH_CALLER, lookupView.getId());
 610  
 
 611  0
         if (StringUtils.isNotBlank(lookupForm.getDocNum())) {
 612  0
             props.put(UifParameters.DOC_NUM, lookupForm.getDocNum());
 613  
         }
 614  
 
 615  0
         if (StringUtils.isNotBlank(lookupForm.getReferencesToRefresh())) {
 616  0
             props.put(KRADConstants.REFERENCES_TO_REFRESH, lookupForm.getReferencesToRefresh());
 617  
         }
 618  
 
 619  0
         List<String> returnKeys = getReturnKeys(lookupView, lookupForm, dataObject);
 620  0
         Map<String, String> returnKeyValues = KRADUtils.getPropertyKeyValuesFromDataObject(returnKeys, dataObject);
 621  
 
 622  0
         for (String returnKey : returnKeyValues.keySet()) {
 623  0
             String returnValue = returnKeyValues.get(returnKey);
 624  0
             if (lookupForm.getFieldConversions().containsKey(returnKey)) {
 625  0
                 returnKey = lookupForm.getFieldConversions().get(returnKey);
 626  
             }
 627  
 
 628  0
             props.put(returnKey, returnValue);
 629  0
         }
 630  
 
 631  0
         return props;
 632  
     }
 633  
 
 634  
     /**
 635  
      * Returns the configured return key property names or if not configured defaults to the primary keys
 636  
      * for the data object class
 637  
      *
 638  
      * @return List<String> property names which should be passed back on the return URL
 639  
      */
 640  
     protected List<String> getReturnKeys(LookupView lookupView, LookupForm lookupForm, Object dataObject) {
 641  
         List<String> returnKeys;
 642  0
         if (lookupForm.getFieldConversions() != null && !lookupForm.getFieldConversions().isEmpty()) {
 643  0
             returnKeys = new ArrayList<String>(lookupForm.getFieldConversions().keySet());
 644  
         } else {
 645  0
             returnKeys = getDataObjectMetaDataService().listPrimaryKeyFieldNames(getDataObjectClass());
 646  
         }
 647  
 
 648  0
         return returnKeys;
 649  
     }
 650  
 
 651  
     /**
 652  
      * @see org.kuali.rice.krad.lookup.Lookupable#getMaintenanceActionLink
 653  
      */
 654  
     public void getMaintenanceActionLink(LinkField actionLinkField, Object model, String maintenanceMethodToCall) {
 655  0
         LookupForm lookupForm = (LookupForm) model;
 656  0
         LookupView lookupView = (LookupView) actionLinkField.getContext().get(UifConstants.ContextVariableNames.VIEW);
 657  0
         Object dataObject = actionLinkField.getContext().get(UifConstants.ContextVariableNames.LINE);
 658  
 
 659  0
         List<String> pkNames = getDataObjectMetaDataService().listPrimaryKeyFieldNames(getDataObjectClass());
 660  
 
 661  
         // build maintenance link href
 662  0
         String href = getActionUrlHref(lookupForm, dataObject, maintenanceMethodToCall, pkNames);
 663  0
         if (StringUtils.isBlank(href)) {
 664  0
             actionLinkField.setRender(false);
 665  0
             return;
 666  
         }
 667  
         // TODO: need to handle returning anchor
 668  0
         actionLinkField.setHrefText(href);
 669  
 
 670  
         // build action title
 671  0
         String prependTitleText = actionLinkField.getLinkLabel() + " " +
 672  
                 getDataDictionaryService().getDataDictionary().getDataObjectEntry(getDataObjectClass().getName())
 673  
                         .getObjectLabel() + " " +
 674  
                 getConfigurationService().getPropertyString(KRADConstants.Lookup.TITLE_ACTION_URL_PREPENDTEXT_PROPERTY);
 675  
 
 676  0
         Map<String, String> primaryKeyValues = KRADUtils.getPropertyKeyValuesFromDataObject(pkNames, dataObject);
 677  0
         String title = LookupInquiryUtils.getLinkTitleText(prependTitleText, getDataObjectClass(), primaryKeyValues);
 678  0
         actionLinkField.setTitle(title);
 679  
 
 680  0
         lookupForm.setAtLeastOneRowHasActions(true);
 681  0
     }
 682  
 
 683  
     /**
 684  
      * Generates a URL to perform a maintenance action on the given result data object
 685  
      *
 686  
      * <p>
 687  
      * Will build a URL containing keys of the data object to invoke the given maintenance action method
 688  
      * within the maintenance controller
 689  
      * </p>
 690  
      *
 691  
      * @param dataObject - data object instance for the line to build the maintenance action link for
 692  
      * @param methodToCall - method name on the maintenance controller that should be invoked
 693  
      * @param pkNames - list of primary key field names for the data object whose key/value pairs will be added to
 694  
      * the maintenance link
 695  
      * @return String URL link for the maintenance action
 696  
      */
 697  
     protected String getActionUrlHref(LookupForm lookupForm, Object dataObject, String methodToCall,
 698  
             List<String> pkNames) {
 699  0
         Properties props = new Properties();
 700  0
         props.put(KRADConstants.DISPATCH_REQUEST_PARAMETER, methodToCall);
 701  
 
 702  0
         Map<String, String> primaryKeyValues = KRADUtils.getPropertyKeyValuesFromDataObject(pkNames, dataObject);
 703  0
         for (String primaryKey : primaryKeyValues.keySet()) {
 704  0
             String primaryKeyValue = primaryKeyValues.get(primaryKey);
 705  
 
 706  0
             props.put(primaryKey, primaryKeyValue);
 707  0
         }
 708  
 
 709  0
         if (StringUtils.isNotBlank(lookupForm.getReturnLocation())) {
 710  0
             props.put(KRADConstants.RETURN_LOCATION_PARAMETER, lookupForm.getReturnLocation());
 711  
         }
 712  
 
 713  0
         props.put(UifParameters.DATA_OBJECT_CLASS_NAME, dataObject.getClass().getName());
 714  0
         props.put(UifParameters.VIEW_TYPE_NAME, UifConstants.ViewType.MAINTENANCE);
 715  
 
 716  0
         return UrlFactory.parameterizeUrl(KRADConstants.Maintenance.REQUEST_MAPPING_MAINTENANCE, props);
 717  
     }
 718  
 
 719  
     /**
 720  
      * Determines if given data object has associated maintenance document that allows new or copy
 721  
      * maintenance
 722  
      * actions
 723  
      *
 724  
      * @return boolean true if the maintenance new or copy action is allowed for the data object instance, false
 725  
      *         otherwise
 726  
      */
 727  
     public boolean allowsMaintenanceNewOrCopyAction() {
 728  0
         boolean allowsNewOrCopy = false;
 729  
 
 730  0
         String maintDocTypeName = getMaintenanceDocumentTypeName();
 731  0
         if (StringUtils.isNotBlank(maintDocTypeName)) {
 732  0
             allowsNewOrCopy = getDataObjectAuthorizationService()
 733  
                     .canCreate(getDataObjectClass(), GlobalVariables.getUserSession().getPerson(), maintDocTypeName);
 734  
         }
 735  
 
 736  0
         return allowsNewOrCopy;
 737  
     }
 738  
 
 739  
     /**
 740  
      * Determines if given data object has associated maintenance document that allows edit maintenance
 741  
      * actions
 742  
      *
 743  
      * @return boolean true if the maintenance edit action is allowed for the data object instance, false otherwise
 744  
      */
 745  
     public boolean allowsMaintenanceEditAction(Object dataObject) {
 746  0
         boolean allowsEdit = false;
 747  
 
 748  0
         String maintDocTypeName = getMaintenanceDocumentTypeName();
 749  0
         if (StringUtils.isNotBlank(maintDocTypeName)) {
 750  0
             allowsEdit = getDataObjectAuthorizationService()
 751  
                     .canMaintain(dataObject, GlobalVariables.getUserSession().getPerson(), maintDocTypeName);
 752  
         }
 753  
 
 754  0
         return allowsEdit;
 755  
     }
 756  
 
 757  
     /**
 758  
      * Determines if given data object has associated maintenance document that allows delete maintenance
 759  
      * actions.
 760  
      *
 761  
      * @return boolean true if the maintenance delete action is allowed for the data object instance, false otherwise
 762  
      */
 763  
     public boolean allowsMaintenanceDeleteAction(Object dataObject) {
 764  0
         boolean allowsMaintain = false;
 765  0
         boolean allowsDelete = false;
 766  
 
 767  0
         String maintDocTypeName = getMaintenanceDocumentTypeName();
 768  0
         if (StringUtils.isNotBlank(maintDocTypeName)) {
 769  0
             allowsMaintain = getDataObjectAuthorizationService()
 770  
                     .canMaintain(dataObject, GlobalVariables.getUserSession().getPerson(), maintDocTypeName);
 771  
         }
 772  
 
 773  0
         allowsDelete = getDocumentDictionaryService().getAllowsRecordDeletion(getDataObjectClass());
 774  
 
 775  0
         return allowsDelete && allowsMaintain;
 776  
     }
 777  
 
 778  
     /**
 779  
      * Returns the maintenance document type associated with the business object class or null if one does not exist.
 780  
      *
 781  
      * @return String representing the maintenance document type name
 782  
      */
 783  
     protected String getMaintenanceDocumentTypeName() {
 784  0
         DocumentDictionaryService dd = getDocumentDictionaryService();
 785  0
         String maintDocTypeName = dd.getMaintenanceDocumentTypeName(getDataObjectClass());
 786  
 
 787  0
         return maintDocTypeName;
 788  
     }
 789  
 
 790  
     /**
 791  
      * Determines whether a given data object that's returned as one of the lookup's results is considered returnable,
 792  
      * which means that for single-value lookups, a "return value" link may be rendered, and for multiple
 793  
      * value lookups, a checkbox is rendered.
 794  
      *
 795  
      * Note that this can be part of an authorization mechanism, but not the complete authorization mechanism.  The
 796  
      * component that invoked the lookup/ lookup caller (e.g. document, nesting lookup, etc.) needs to check
 797  
      * that the object that was passed to it was returnable as well because there are ways around this method
 798  
      * (e.g. crafting a custom return URL).
 799  
      *
 800  
      * @param dataObject - an object from the search result set
 801  
      * @return true if the row is returnable and false if it is not
 802  
      */
 803  
     protected boolean isResultReturnable(Object dataObject) {
 804  0
         return true;
 805  
     }
 806  
 
 807  
     /**
 808  
      * @see org.kuali.rice.krad.lookup.Lookupable#setDataObjectClass
 809  
      */
 810  
     @Override
 811  
     public void setDataObjectClass(Class<?> dataObjectClass) {
 812  0
         this.dataObjectClass = dataObjectClass;
 813  0
     }
 814  
 
 815  
     /**
 816  
      * @see org.kuali.rice.krad.lookup.Lookupable#getDataObjectClass
 817  
      */
 818  
     @Override
 819  
     public Class<?> getDataObjectClass() {
 820  0
         return this.dataObjectClass;
 821  
     }
 822  
 
 823  
     /**
 824  
      * @see org.kuali.rice.krad.lookup.Lookupable#setFieldConversions
 825  
      */
 826  
     @Override
 827  
     public void setFieldConversions(Map<String, String> fieldConversions) {
 828  0
         this.fieldConversions = fieldConversions;
 829  0
     }
 830  
 
 831  
     /**
 832  
      * @see org.kuali.rice.krad.lookup.Lookupable#setReadOnlyFieldsList
 833  
      */
 834  
     @Override
 835  
     public void setReadOnlyFieldsList(List<String> readOnlyFieldsList) {
 836  0
         this.readOnlyFieldsList = readOnlyFieldsList;
 837  0
     }
 838  
 
 839  
     public Map<String, String> getParameters() {
 840  0
         return parameters;
 841  
     }
 842  
 
 843  
     public void setParameters(Map<String, String> parameters) {
 844  0
         this.parameters = parameters;
 845  0
     }
 846  
 
 847  
     public List<String> getDefaultSortAttributeNames() {
 848  0
         return defaultSortAttributeNames;
 849  
     }
 850  
 
 851  
     public void setDefaultSortAttributeNames(List<String> defaultSortAttributeNames) {
 852  0
         this.defaultSortAttributeNames = defaultSortAttributeNames;
 853  0
     }
 854  
 
 855  
     public boolean isSortAscending() {
 856  0
         return sortAscending;
 857  
     }
 858  
 
 859  
     public void setSortAscending(boolean sortAscending) {
 860  0
         this.sortAscending = sortAscending;
 861  0
     }
 862  
 
 863  
     public List<String> getReadOnlyFieldsList() {
 864  0
         return readOnlyFieldsList;
 865  
     }
 866  
 
 867  
     public Map<String, String> getFieldConversions() {
 868  0
         return fieldConversions;
 869  
     }
 870  
 
 871  
     protected ConfigurationService getConfigurationService() {
 872  0
         if (configurationService == null) {
 873  0
             this.configurationService = KRADServiceLocator.getKualiConfigurationService();
 874  
         }
 875  0
         return configurationService;
 876  
     }
 877  
 
 878  
     public void setConfigurationService(ConfigurationService configurationService) {
 879  0
         this.configurationService = configurationService;
 880  0
     }
 881  
 
 882  
     protected DataObjectAuthorizationService getDataObjectAuthorizationService() {
 883  0
         if (dataObjectAuthorizationService == null) {
 884  0
             this.dataObjectAuthorizationService = KRADServiceLocatorWeb.getDataObjectAuthorizationService();
 885  
         }
 886  0
         return dataObjectAuthorizationService;
 887  
     }
 888  
 
 889  
     public void setDataObjectAuthorizationService(DataObjectAuthorizationService dataObjectAuthorizationService) {
 890  0
         this.dataObjectAuthorizationService = dataObjectAuthorizationService;
 891  0
     }
 892  
 
 893  
     protected DataObjectMetaDataService getDataObjectMetaDataService() {
 894  0
         if (dataObjectMetaDataService == null) {
 895  0
             this.dataObjectMetaDataService = KRADServiceLocatorWeb.getDataObjectMetaDataService();
 896  
         }
 897  0
         return dataObjectMetaDataService;
 898  
     }
 899  
 
 900  
     public void setDataObjectMetaDataService(DataObjectMetaDataService dataObjectMetaDataService) {
 901  0
         this.dataObjectMetaDataService = dataObjectMetaDataService;
 902  0
     }
 903  
 
 904  
     public DocumentDictionaryService getDocumentDictionaryService() {
 905  0
         if (documentDictionaryService == null) {
 906  0
             documentDictionaryService = KRADServiceLocatorWeb.getDocumentDictionaryService();
 907  
         }
 908  0
         return documentDictionaryService;
 909  
     }
 910  
 
 911  
     public void setDocumentDictionaryService(DocumentDictionaryService documentDictionaryService) {
 912  0
         this.documentDictionaryService = documentDictionaryService;
 913  0
     }
 914  
 
 915  
     protected LookupService getLookupService() {
 916  0
         if (lookupService == null) {
 917  0
             this.lookupService = KRADServiceLocatorWeb.getLookupService();
 918  
         }
 919  0
         return lookupService;
 920  
     }
 921  
 
 922  
     public void setLookupService(LookupService lookupService) {
 923  0
         this.lookupService = lookupService;
 924  0
     }
 925  
 
 926  
     protected EncryptionService getEncryptionService() {
 927  0
         if (encryptionService == null) {
 928  0
             this.encryptionService = CoreApiServiceLocator.getEncryptionService();
 929  
         }
 930  0
         return encryptionService;
 931  
     }
 932  
 
 933  
     public void setEncryptionService(EncryptionService encryptionService) {
 934  0
         this.encryptionService = encryptionService;
 935  0
     }
 936  
 }