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