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