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