Coverage Report - org.kuali.rice.kns.lookup.KualiLookupableHelperServiceImpl
 
Classes in this File Line Coverage Branch Coverage Complexity
KualiLookupableHelperServiceImpl
0%
0/152
0%
0/82
5.667
 
 1  
 /*
 2  
  * Copyright 2006-2007 The Kuali Foundation
 3  
  *
 4  
  * Licensed under the Educational Community License, Version 2.0 (the "License");
 5  
  * you may not use this file except in compliance with the License.
 6  
  * You may obtain a copy of the License at
 7  
  *
 8  
  * http://www.opensource.org/licenses/ecl2.php
 9  
  *
 10  
  * Unless required by applicable law or agreed to in writing, software
 11  
  * distributed under the License is distributed on an "AS IS" BASIS,
 12  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13  
  * See the License for the specific language governing permissions and
 14  
  * limitations under the License.
 15  
  */
 16  
 package org.kuali.rice.kns.lookup;
 17  
 
 18  
 import org.apache.commons.beanutils.PropertyUtils;
 19  
 import org.apache.commons.lang.StringUtils;
 20  
 import org.kuali.rice.core.api.encryption.EncryptionService;
 21  
 import org.kuali.rice.core.api.search.SearchOperator;
 22  
 import org.kuali.rice.krad.bo.BusinessObject;
 23  
 import org.kuali.rice.krad.bo.ExternalizableBusinessObject;
 24  
 import org.kuali.rice.krad.datadictionary.BusinessObjectEntry;
 25  
 import org.kuali.rice.krad.datadictionary.RelationshipDefinition;
 26  
 import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
 27  
 import org.kuali.rice.krad.service.ModuleService;
 28  
 import org.kuali.rice.krad.util.BeanPropertyComparator;
 29  
 import org.kuali.rice.krad.util.ExternalizableBusinessObjectUtils;
 30  
 import org.kuali.rice.krad.util.KRADConstants;
 31  
 import org.kuali.rice.krad.util.ObjectUtils;
 32  
 import org.springframework.transaction.annotation.Transactional;
 33  
 
 34  
 import java.security.GeneralSecurityException;
 35  
 import java.util.ArrayList;
 36  
 import java.util.Collections;
 37  
 import java.util.HashMap;
 38  
 import java.util.HashSet;
 39  
 import java.util.Iterator;
 40  
 import java.util.List;
 41  
 import java.util.Map;
 42  
 import java.util.Set;
 43  
 
 44  
 @Transactional
 45  0
 public class KualiLookupableHelperServiceImpl extends AbstractLookupableHelperServiceImpl {
 46  
 
 47  0
     protected static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(KualiLookupableHelperServiceImpl.class);
 48  0
     protected boolean searchUsingOnlyPrimaryKeyValues = false;
 49  
 
 50  
 
 51  
     /**
 52  
      * Uses Lookup Service to provide a basic search.
 53  
      *
 54  
      * @param fieldValues - Map containing prop name keys and search values
 55  
      *
 56  
      * @return List found business objects
 57  
      * @see LookupableHelperService#getSearchResults(java.util.Map)
 58  
      */
 59  
     public List<? extends BusinessObject> getSearchResults(Map<String, String> fieldValues) {
 60  0
         return getSearchResultsHelper(
 61  
                 org.kuali.rice.krad.lookup.LookupUtils.forceUppercase(getBusinessObjectClass(), fieldValues), false);
 62  
     }
 63  
 
 64  
 
 65  
     /**
 66  
      * Uses Lookup Service to provide a basic unbounded search.
 67  
      *
 68  
      * @param fieldValues - Map containing prop name keys and search values
 69  
      *
 70  
      * @return List found business objects
 71  
      * @see LookupableHelperService#getSearchResultsUnbounded(java.util.Map)
 72  
      */
 73  
     public List<? extends BusinessObject> getSearchResultsUnbounded(Map<String, String> fieldValues) {
 74  0
         return getSearchResultsHelper(
 75  
                 org.kuali.rice.krad.lookup.LookupUtils.forceUppercase(getBusinessObjectClass(), fieldValues), true);
 76  
     }
 77  
 
 78  
     // TODO: Fix? - this does not handle nested properties within the EBO.
 79  
 
 80  
     /**
 81  
      * Check whether the given property represents a property within an EBO starting
 82  
      * with the sampleBo object given.  This is used to determine if a criteria needs
 83  
      * to be applied to the EBO first, before sending to the normal lookup DAO.
 84  
      */
 85  
     protected boolean isExternalBusinessObjectProperty(Object sampleBo, String propertyName) {
 86  
         try {
 87  0
                 if ( propertyName.indexOf( "." ) > 0 && !StringUtils.contains( propertyName, "add." ) ) {
 88  0
                         Class propertyClass = PropertyUtils.getPropertyType(
 89  
                                                 sampleBo, StringUtils.substringBeforeLast( propertyName, "." ) );
 90  0
                         if ( propertyClass != null ) {
 91  0
                                 return ExternalizableBusinessObjectUtils.isExternalizableBusinessObjectInterface( propertyClass );
 92  
                         } else {
 93  0
                                 if ( LOG.isDebugEnabled() ) {
 94  0
                                         LOG.debug( "unable to get class for " + StringUtils.substringBeforeLast( propertyName, "." ) + " on " + sampleBo.getClass().getName() );
 95  
                                 }
 96  
                         }
 97  
                 }
 98  0
         } catch (Exception e) {
 99  0
                 LOG.debug("Unable to determine type of property for " + sampleBo.getClass().getName() + "/" + propertyName, e );
 100  0
         }
 101  0
         return false;
 102  
     }
 103  
 
 104  
     /**
 105  
      * Get the name of the property which represents the ExternalizableBusinessObject for the given property.
 106  
      *
 107  
      * This method can not handle nested properties within the EBO.
 108  
      *
 109  
      * Returns null if the property is not a nested property or is part of an add line.
 110  
      */
 111  
     protected String getExternalBusinessObjectProperty(Object sampleBo, String propertyName) {
 112  0
             if ( propertyName.indexOf( "." ) > 0 && !StringUtils.contains( propertyName, "add." ) ) {
 113  0
                     return StringUtils.substringBeforeLast( propertyName, "." );
 114  
             }
 115  0
         return null;
 116  
     }
 117  
 
 118  
     /**
 119  
      * Checks whether any of the fieldValues being passed refer to a property within an ExternalizableBusinessObject.
 120  
      */
 121  
     protected boolean hasExternalBusinessObjectProperty(Class boClass, Map<String,String> fieldValues ) {
 122  
             try {
 123  0
                     Object sampleBo = boClass.newInstance();
 124  0
                     for ( String key : fieldValues.keySet() ) {
 125  0
                             if ( isExternalBusinessObjectProperty( sampleBo, key )) {
 126  0
                                     return true;
 127  
                             }
 128  
                     }
 129  0
             } catch ( Exception ex ) {
 130  0
                 LOG.debug("Unable to check " + boClass + " for EBO properties.", ex );
 131  0
             }
 132  0
             return false;
 133  
     }
 134  
 
 135  
     /**
 136  
      * Returns a map stripped of any properties which refer to ExternalizableBusinessObjects.  These values may not be passed into the
 137  
      * lookup service, since the objects they refer to are not in the local database.
 138  
      */
 139  
     protected Map<String,String> removeExternalizableBusinessObjectFieldValues(Class boClass, Map<String,String> fieldValues ) {
 140  0
             Map<String,String> eboFieldValues = new HashMap<String,String>();
 141  
             try {
 142  0
                     Object sampleBo = boClass.newInstance();
 143  0
                     for ( String key : fieldValues.keySet() ) {
 144  0
                             if ( !isExternalBusinessObjectProperty( sampleBo, key )) {
 145  0
                                     eboFieldValues.put( key, fieldValues.get( key ) );
 146  
                             }
 147  
                     }
 148  0
             } catch ( Exception ex ) {
 149  0
                 LOG.debug("Unable to check " + boClass + " for EBO properties.", ex );
 150  0
             }
 151  0
             return eboFieldValues;
 152  
     }
 153  
 
 154  
     /**
 155  
      * Return the EBO fieldValue entries explicitly for the given eboPropertyName.  (I.e., any properties with the given
 156  
      * property name as a prefix.
 157  
      */
 158  
     protected Map<String,String> getExternalizableBusinessObjectFieldValues(String eboPropertyName, Map<String,String> fieldValues ) {
 159  0
             Map<String,String> eboFieldValues = new HashMap<String,String>();
 160  0
             for ( String key : fieldValues.keySet() ) {
 161  0
                     if ( key.startsWith( eboPropertyName + "." ) ) {
 162  0
                             eboFieldValues.put( StringUtils.substringAfterLast( key, "." ), fieldValues.get( key ) );
 163  
                     }
 164  
             }
 165  0
             return eboFieldValues;
 166  
     }
 167  
 
 168  
     /**
 169  
      * Get the complete list of all properties referenced in the fieldValues that are ExternalizableBusinessObjects.
 170  
      *
 171  
      * This is a list of the EBO object references themselves, not of the properties within them.
 172  
      */
 173  
     protected List<String> getExternalizableBusinessObjectProperties(Class boClass, Map<String,String> fieldValues ) {
 174  0
             Set<String> eboPropertyNames = new HashSet<String>();
 175  
             try {
 176  0
                     Object sampleBo = boClass.newInstance();
 177  0
                     for ( String key : fieldValues.keySet() ) {
 178  0
                             if ( isExternalBusinessObjectProperty( sampleBo, key )) {
 179  0
                                     eboPropertyNames.add( StringUtils.substringBeforeLast( key, "." ) );
 180  
                             }
 181  
                     }
 182  0
             } catch ( Exception ex ) {
 183  0
                 LOG.debug("Unable to check " + boClass + " for EBO properties.", ex );
 184  0
             }
 185  0
             return new ArrayList<String>(eboPropertyNames);
 186  
     }
 187  
 
 188  
     /**
 189  
      * Given an property on the main BO class, return the defined type of the ExternalizableBusinessObject.  This will be used
 190  
      * by other code to determine the correct module service to call for the lookup.
 191  
      *
 192  
      * @param boClass
 193  
      * @param propertyName
 194  
      * @return
 195  
      */
 196  
     protected Class<? extends ExternalizableBusinessObject> getExternalizableBusinessObjectClass(Class boClass, String propertyName) {
 197  
         try {
 198  0
                 return PropertyUtils.getPropertyType(
 199  
                                         boClass.newInstance(), StringUtils.substringBeforeLast( propertyName, "." ) );
 200  0
         } catch (Exception e) {
 201  0
                 LOG.debug("Unable to determine type of property for " + boClass.getName() + "/" + propertyName, e );
 202  
         }
 203  0
         return null;
 204  
     }
 205  
 
 206  
     /**
 207  
      *
 208  
      * This method does the actual search, with the parameters specified, and returns the result.
 209  
      *
 210  
      * NOTE that it will not do any upper-casing based on the DD forceUppercase. That is handled through an external call to
 211  
      * LookupUtils.forceUppercase().
 212  
      *
 213  
      * @param fieldValues A Map of the fieldNames and fieldValues to be searched on.
 214  
      * @param unbounded Whether the results should be bounded or not to a certain max size.
 215  
      * @return A List of search results.
 216  
      *
 217  
      */
 218  
     protected List<? extends BusinessObject> getSearchResultsHelper(Map<String, String> fieldValues, boolean unbounded) {
 219  
         // remove hidden fields
 220  0
         LookupUtils.removeHiddenCriteriaFields(getBusinessObjectClass(), fieldValues);
 221  
 
 222  0
         searchUsingOnlyPrimaryKeyValues = getLookupService().allPrimaryKeyValuesPresentAndNotWildcard(getBusinessObjectClass(), fieldValues);
 223  
 
 224  0
         setBackLocation(fieldValues.get(KRADConstants.BACK_LOCATION));
 225  0
         setDocFormKey(fieldValues.get(KRADConstants.DOC_FORM_KEY));
 226  0
         setReferencesToRefresh(fieldValues.get(KRADConstants.REFERENCES_TO_REFRESH));
 227  
         List searchResults;
 228  0
             Map<String,String> nonBlankFieldValues = new HashMap<String, String>();
 229  0
             for (String fieldName : fieldValues.keySet()) {
 230  0
                     String fieldValue = fieldValues.get(fieldName);
 231  0
                     if (StringUtils.isNotBlank(fieldValue) ) {
 232  0
                             if (fieldValue.endsWith(EncryptionService.ENCRYPTION_POST_PREFIX)) {
 233  0
                                     String encryptedValue = StringUtils.removeEnd(fieldValue, EncryptionService.ENCRYPTION_POST_PREFIX);
 234  
                                     try {
 235  0
                                             fieldValue = getEncryptionService().decrypt(encryptedValue);
 236  
                                     }
 237  0
                                     catch (GeneralSecurityException e) {
 238  0
                                     LOG.error("Error decrypting value for business object " + getBusinessObjectService() + " attribute " + fieldName, e);
 239  0
                                     throw new RuntimeException("Error decrypting value for business object " + getBusinessObjectService() + " attribute " + fieldName, e);
 240  0
                             }
 241  
                             }
 242  0
                             nonBlankFieldValues.put(fieldName, fieldValue);
 243  
                     }
 244  0
             }
 245  
 
 246  
         // If this class is an EBO, just call the module service to get the results
 247  0
         if ( ExternalizableBusinessObject.class.isAssignableFrom( getBusinessObjectClass() ) ) {
 248  0
                 ModuleService eboModuleService = KRADServiceLocatorWeb.getKualiModuleService().getResponsibleModuleService( getBusinessObjectClass() );
 249  0
                 BusinessObjectEntry ddEntry = eboModuleService.getExternalizableBusinessObjectDictionaryEntry(getBusinessObjectClass());
 250  0
                 Map<String,String> filteredFieldValues = new HashMap<String, String>();
 251  0
                 for (String fieldName : nonBlankFieldValues.keySet()) {
 252  0
                         if (ddEntry.getAttributeNames().contains(fieldName)) {
 253  0
                                 filteredFieldValues.put(fieldName, nonBlankFieldValues.get(fieldName));
 254  
                         }
 255  
                 }
 256  0
                 searchResults = eboModuleService.getExternalizableBusinessObjectsListForLookup(getBusinessObjectClass(), (Map)filteredFieldValues, unbounded);
 257  
         // if any of the properties refer to an embedded EBO, call the EBO lookups first and apply to the local lookup
 258  0
         } else if ( hasExternalBusinessObjectProperty( getBusinessObjectClass(), nonBlankFieldValues ) ) {
 259  0
                 if ( LOG.isDebugEnabled() ) {
 260  0
                         LOG.debug( "has EBO reference: " + getBusinessObjectClass() );
 261  0
                         LOG.debug( "properties: " + nonBlankFieldValues );
 262  
                 }
 263  
                 // remove the EBO criteria
 264  0
                 Map<String,String> nonEboFieldValues = removeExternalizableBusinessObjectFieldValues( getBusinessObjectClass(), nonBlankFieldValues );
 265  0
                 if ( LOG.isDebugEnabled() ) {
 266  0
                         LOG.debug( "Non EBO properties removed: " + nonEboFieldValues );
 267  
                 }
 268  
                 // get the list of EBO properties attached to this object
 269  0
                 List<String> eboPropertyNames = getExternalizableBusinessObjectProperties( getBusinessObjectClass(), nonBlankFieldValues );
 270  0
                 if ( LOG.isDebugEnabled() ) {
 271  0
                         LOG.debug( "EBO properties: " + eboPropertyNames );
 272  
                 }
 273  
                 // loop over those properties
 274  0
                 for ( String eboPropertyName : eboPropertyNames ) {
 275  
                         // extract the properties as known to the EBO
 276  0
                         Map<String,String> eboFieldValues = getExternalizableBusinessObjectFieldValues( eboPropertyName, nonBlankFieldValues );
 277  0
                     if ( LOG.isDebugEnabled() ) {
 278  0
                             LOG.debug( "EBO properties for master EBO property: " + eboPropertyName );
 279  0
                             LOG.debug( "properties: " + eboFieldValues );
 280  
                     }
 281  
                     // run search against attached EBO's module service
 282  0
                     ModuleService eboModuleService = KRADServiceLocatorWeb.getKualiModuleService().getResponsibleModuleService( getExternalizableBusinessObjectClass( getBusinessObjectClass(), eboPropertyName) );
 283  
                     // KULRICE-4401 made eboResults an empty list and only filled if service is found.
 284  0
                           List eboResults = Collections.emptyList();
 285  0
                           if (eboModuleService != null) 
 286  
                           {
 287  0
                                   eboResults = eboModuleService.getExternalizableBusinessObjectsListForLookup( getExternalizableBusinessObjectClass( getBusinessObjectClass(), eboPropertyName), (Map)eboFieldValues, unbounded);
 288  
                           }
 289  
                                   else
 290  
                           {
 291  0
                                   LOG.debug( "EBO ModuleService is null: " + eboPropertyName );
 292  
                           }
 293  
                         // get the mapping/relationship between the EBO object and it's parent object
 294  
                         // use that to adjust the fieldValues
 295  
 
 296  
                         // get the parent property type
 297  
                         Class eboParentClass;
 298  
                         String eboParentPropertyName;
 299  0
                         if ( ObjectUtils.isNestedAttribute( eboPropertyName ) ) {
 300  0
                                 eboParentPropertyName = StringUtils.substringBeforeLast( eboPropertyName, "." );
 301  
                                 try {
 302  0
                                         eboParentClass = PropertyUtils.getPropertyType( getBusinessObjectClass().newInstance(), eboParentPropertyName );
 303  0
                                 } catch ( Exception ex ) {
 304  0
                                         throw new RuntimeException( "Unable to create an instance of the business object class: " + getBusinessObjectClass().getName(), ex );
 305  0
                                 }
 306  
                         } else {
 307  0
                                 eboParentClass = getBusinessObjectClass();
 308  0
                                 eboParentPropertyName = null;
 309  
                         }
 310  0
                         if ( LOG.isDebugEnabled() ) {
 311  0
                                 LOG.debug( "determined EBO parent class/property name: " + eboParentClass + "/" + eboParentPropertyName );
 312  
                         }
 313  
                         // look that up in the DD (BOMDS)
 314  
                         // find the appropriate relationship
 315  
                         // CHECK THIS: what if eboPropertyName is a nested attribute - need to strip off the eboParentPropertyName if not null
 316  0
                         RelationshipDefinition rd = getBusinessObjectMetaDataService().getBusinessObjectRelationshipDefinition( eboParentClass, eboPropertyName );
 317  0
                         if ( LOG.isDebugEnabled() ) {
 318  0
                                 LOG.debug( "Obtained RelationshipDefinition for " + eboPropertyName );
 319  0
                                 LOG.debug( rd );
 320  
                         }
 321  
 
 322  
                         // copy the needed properties (primary only) to the field values
 323  
                         // KULRICE-4446 do so only if the relationship definition exists
 324  
                         // NOTE: this will work only for single-field PK unless the ORM layer is directly involved
 325  
                         // (can't make (field1,field2) in ( (v1,v2),(v3,v4) ) style queries in the lookup framework
 326  0
                         if ( ObjectUtils.isNotNull(rd)) {
 327  0
                                 if ( rd.getPrimitiveAttributes().size() > 1 ) {
 328  0
                                         throw new RuntimeException( "EBO Links don't work for relationships with multiple-field primary keys." );
 329  
                                 }
 330  0
                                 String boProperty = rd.getPrimitiveAttributes().get( 0 ).getSourceName();
 331  0
                                 String eboProperty = rd.getPrimitiveAttributes().get( 0 ).getTargetName();
 332  0
                                 StringBuffer boPropertyValue = new StringBuffer();
 333  
                                 // loop over the results, making a string that the lookup DAO will convert into an
 334  
                                 // SQL "IN" clause
 335  0
                                 for ( Object ebo : eboResults ) {
 336  0
                                         if ( boPropertyValue.length() != 0 ) {
 337  0
                                                 boPropertyValue.append( SearchOperator.OR.op() );
 338  
                                         }
 339  
                                         try {
 340  0
                                                 boPropertyValue.append( PropertyUtils.getProperty( ebo, eboProperty ).toString() );
 341  0
                                         } catch ( Exception ex ) {
 342  0
                                                 LOG.warn( "Unable to get value for " + eboProperty + " on " + ebo );
 343  0
                                         }
 344  
                                 }
 345  0
                                 if ( eboParentPropertyName == null ) {
 346  
                                         // non-nested property containing the EBO
 347  0
                                         nonEboFieldValues.put( boProperty, boPropertyValue.toString() );
 348  
                                 } else {
 349  
                                         // property nested within the main searched-for BO that contains the EBO
 350  0
                                         nonEboFieldValues.put( eboParentPropertyName + "." + boProperty, boPropertyValue.toString() );
 351  
                                 }
 352  
                         }
 353  0
                 }
 354  0
                 if ( LOG.isDebugEnabled() ) {
 355  0
                         LOG.debug( "Passing these results into the lookup service: " + nonEboFieldValues );
 356  
                 }
 357  
                 // add those results as criteria
 358  
                 // run the normal search (but with the EBO critieria added)
 359  0
                     searchResults = (List) getLookupService().findCollectionBySearchHelper(getBusinessObjectClass(), nonEboFieldValues, unbounded);
 360  0
         } else {
 361  0
             searchResults = (List) getLookupService().findCollectionBySearchHelper(getBusinessObjectClass(), nonBlankFieldValues, unbounded);
 362  
         }
 363  
         
 364  0
         if (searchResults == null) {
 365  0
                 searchResults = new ArrayList();
 366  
         }
 367  
 
 368  
         // sort list if default sort column given
 369  0
         List defaultSortColumns = getDefaultSortColumns();
 370  0
         if (defaultSortColumns.size() > 0) {
 371  0
             Collections.sort(searchResults, new BeanPropertyComparator(defaultSortColumns, true));
 372  
         }
 373  0
         return searchResults;
 374  
     }
 375  
 
 376  
 
 377  
     /**
 378  
      * @see LookupableHelperService#isSearchUsingOnlyPrimaryKeyValues()
 379  
      */
 380  
     @Override
 381  
     public boolean isSearchUsingOnlyPrimaryKeyValues() {
 382  0
         return searchUsingOnlyPrimaryKeyValues;
 383  
 }
 384  
 
 385  
 
 386  
     /**
 387  
      * Returns a comma delimited list of primary key field labels, to be used on the UI to tell the user which fields were used to search
 388  
      *
 389  
      * These labels are generated from the DD definitions for the lookup fields
 390  
      *
 391  
      * @return a comma separated list of field attribute names.  If no fields found, returns "N/A"
 392  
      * @see LookupableHelperService#isSearchUsingOnlyPrimaryKeyValues()
 393  
      * @see LookupableHelperService#getPrimaryKeyFieldLabels()
 394  
      */
 395  
     @Override
 396  
     public String getPrimaryKeyFieldLabels() {
 397  0
         StringBuilder buf = new StringBuilder();
 398  0
         List<String> primaryKeyFieldNames = getBusinessObjectMetaDataService().listPrimaryKeyFieldNames(getBusinessObjectClass());
 399  0
         Iterator<String> pkIter = primaryKeyFieldNames.iterator();
 400  0
         while (pkIter.hasNext()) {
 401  0
             String pkFieldName = (String) pkIter.next();
 402  0
             buf.append(getDataDictionaryService().getAttributeLabel(getBusinessObjectClass(), pkFieldName));
 403  0
             if (pkIter.hasNext()) {
 404  0
                 buf.append(", ");
 405  
             }
 406  0
         }
 407  0
         return buf.length() == 0 ? KRADConstants.NOT_AVAILABLE_STRING : buf.toString();
 408  
     }
 409  
 
 410  
 
 411  
 }
 412