001/** 002 * Copyright 2005-2016 The Kuali Foundation 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.kuali.rice.kns.lookup; 017 018import org.apache.commons.beanutils.PropertyUtils; 019import org.apache.commons.lang.StringUtils; 020import org.kuali.rice.core.api.CoreApiServiceLocator; 021import org.kuali.rice.core.api.encryption.EncryptionService; 022import org.kuali.rice.core.api.search.SearchOperator; 023import org.kuali.rice.krad.bo.BusinessObject; 024import org.kuali.rice.krad.bo.ExternalizableBusinessObject; 025import org.kuali.rice.krad.datadictionary.BusinessObjectEntry; 026import org.kuali.rice.krad.datadictionary.RelationshipDefinition; 027import org.kuali.rice.krad.service.KRADServiceLocatorWeb; 028import org.kuali.rice.krad.service.ModuleService; 029import org.kuali.rice.krad.util.BeanPropertyComparator; 030import org.kuali.rice.krad.util.ExternalizableBusinessObjectUtils; 031import org.kuali.rice.krad.util.KRADConstants; 032import org.kuali.rice.krad.util.ObjectUtils; 033import org.springframework.transaction.annotation.Transactional; 034 035import java.security.GeneralSecurityException; 036import java.util.ArrayList; 037import java.util.Collections; 038import java.util.HashMap; 039import java.util.HashSet; 040import java.util.Iterator; 041import java.util.List; 042import java.util.Map; 043import java.util.Set; 044 045/** 046 * @deprecated Use {@link org.kuali.rice.krad.lookup.LookupableImpl}. 047 */ 048@Deprecated 049@Transactional 050public class KualiLookupableHelperServiceImpl extends AbstractLookupableHelperServiceImpl { 051 052 protected static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(KualiLookupableHelperServiceImpl.class); 053 protected boolean searchUsingOnlyPrimaryKeyValues = false; 054 055 056 /** 057 * Uses Lookup Service to provide a basic search. 058 * 059 * @param fieldValues - Map containing prop name keys and search values 060 * 061 * @return List found business objects 062 * @see LookupableHelperService#getSearchResults(java.util.Map) 063 */ 064 public List<? extends BusinessObject> getSearchResults(Map<String, String> fieldValues) { 065 return getSearchResultsHelper( 066 org.kuali.rice.krad.lookup.LookupUtils.forceUppercase(getBusinessObjectClass(), fieldValues), false); 067 } 068 069 070 /** 071 * Uses Lookup Service to provide a basic unbounded search. 072 * 073 * @param fieldValues - Map containing prop name keys and search values 074 * 075 * @return List found business objects 076 * @see LookupableHelperService#getSearchResultsUnbounded(java.util.Map) 077 */ 078 public List<? extends BusinessObject> getSearchResultsUnbounded(Map<String, String> fieldValues) { 079 return getSearchResultsHelper( 080 org.kuali.rice.krad.lookup.LookupUtils.forceUppercase(getBusinessObjectClass(), fieldValues), true); 081 } 082 083 // TODO: Fix? - this does not handle nested properties within the EBO. 084 085 /** 086 * Check whether the given property represents a property within an EBO starting 087 * with the sampleBo object given. This is used to determine if a criteria needs 088 * to be applied to the EBO first, before sending to the normal lookup DAO. 089 */ 090 protected boolean isExternalBusinessObjectProperty(Object sampleBo, String propertyName) { 091 try { 092 if ( propertyName.indexOf( "." ) > 0 && !StringUtils.contains( propertyName, "add." ) ) { 093 Class propertyClass = PropertyUtils.getPropertyType( 094 sampleBo, StringUtils.substringBeforeLast( propertyName, "." ) ); 095 if ( propertyClass != null ) { 096 return ExternalizableBusinessObjectUtils.isExternalizableBusinessObjectInterface( propertyClass ); 097 } else { 098 if ( LOG.isDebugEnabled() ) { 099 LOG.debug( "unable to get class for " + StringUtils.substringBeforeLast( propertyName, "." ) + " on " + sampleBo.getClass().getName() ); 100 } 101 } 102 } 103 } catch (Exception e) { 104 LOG.debug("Unable to determine type of property for " + sampleBo.getClass().getName() + "/" + propertyName, e ); 105 } 106 return false; 107 } 108 109 /** 110 * Get the name of the property which represents the ExternalizableBusinessObject for the given property. 111 * 112 * This method can not handle nested properties within the EBO. 113 * 114 * Returns null if the property is not a nested property or is part of an add line. 115 */ 116 protected String getExternalBusinessObjectProperty(Object sampleBo, String propertyName) { 117 if ( propertyName.indexOf( "." ) > 0 && !StringUtils.contains( propertyName, "add." ) ) { 118 return StringUtils.substringBeforeLast( propertyName, "." ); 119 } 120 return null; 121 } 122 123 /** 124 * Checks whether any of the fieldValues being passed refer to a property within an ExternalizableBusinessObject. 125 */ 126 protected boolean hasExternalBusinessObjectProperty(Class boClass, Map<String,String> fieldValues ) { 127 try { 128 Object sampleBo = boClass.newInstance(); 129 for ( String key : fieldValues.keySet() ) { 130 if ( isExternalBusinessObjectProperty( sampleBo, key )) { 131 return true; 132 } 133 } 134 } catch ( Exception ex ) { 135 LOG.debug("Unable to check " + boClass + " for EBO properties.", ex ); 136 } 137 return false; 138 } 139 140 /** 141 * Returns a map stripped of any properties which refer to ExternalizableBusinessObjects. These values may not be passed into the 142 * lookup service, since the objects they refer to are not in the local database. 143 */ 144 protected Map<String,String> removeExternalizableBusinessObjectFieldValues(Class boClass, Map<String,String> fieldValues ) { 145 Map<String,String> eboFieldValues = new HashMap<String,String>(); 146 try { 147 Object sampleBo = boClass.newInstance(); 148 for ( String key : fieldValues.keySet() ) { 149 if ( !isExternalBusinessObjectProperty( sampleBo, key )) { 150 eboFieldValues.put( key, fieldValues.get( key ) ); 151 } 152 } 153 } catch ( Exception ex ) { 154 LOG.debug("Unable to check " + boClass + " for EBO properties.", ex ); 155 } 156 return eboFieldValues; 157 } 158 159 /** 160 * Return the EBO fieldValue entries explicitly for the given eboPropertyName. (I.e., any properties with the given 161 * property name as a prefix. 162 */ 163 protected Map<String,String> getExternalizableBusinessObjectFieldValues(String eboPropertyName, Map<String,String> fieldValues ) { 164 Map<String,String> eboFieldValues = new HashMap<String,String>(); 165 for ( String key : fieldValues.keySet() ) { 166 if ( key.startsWith( eboPropertyName + "." ) ) { 167 eboFieldValues.put( StringUtils.substringAfterLast( key, "." ), fieldValues.get( key ) ); 168 } 169 } 170 return eboFieldValues; 171 } 172 173 /** 174 * Get the complete list of all properties referenced in the fieldValues that are ExternalizableBusinessObjects. 175 * 176 * This is a list of the EBO object references themselves, not of the properties within them. 177 */ 178 protected List<String> getExternalizableBusinessObjectProperties(Class boClass, Map<String,String> fieldValues ) { 179 Set<String> eboPropertyNames = new HashSet<String>(); 180 try { 181 Object sampleBo = boClass.newInstance(); 182 for ( String key : fieldValues.keySet() ) { 183 if ( isExternalBusinessObjectProperty( sampleBo, key )) { 184 eboPropertyNames.add( StringUtils.substringBeforeLast( key, "." ) ); 185 } 186 } 187 } catch ( Exception ex ) { 188 LOG.debug("Unable to check " + boClass + " for EBO properties.", ex ); 189 } 190 return new ArrayList<String>(eboPropertyNames); 191 } 192 193 /** 194 * Given an property on the main BO class, return the defined type of the ExternalizableBusinessObject. This will be used 195 * by other code to determine the correct module service to call for the lookup. 196 * 197 * @param boClass 198 * @param propertyName 199 * @return 200 */ 201 protected Class<? extends ExternalizableBusinessObject> getExternalizableBusinessObjectClass(Class boClass, String propertyName) { 202 try { 203 return PropertyUtils.getPropertyType( 204 boClass.newInstance(), StringUtils.substringBeforeLast( propertyName, "." ) ); 205 } catch (Exception e) { 206 LOG.debug("Unable to determine type of property for " + boClass.getName() + "/" + propertyName, e ); 207 } 208 return null; 209 } 210 211 /** 212 * 213 * This method does the actual search, with the parameters specified, and returns the result. 214 * 215 * NOTE that it will not do any upper-casing based on the DD forceUppercase. That is handled through an external call to 216 * LookupUtils.forceUppercase(). 217 * 218 * @param fieldValues A Map of the fieldNames and fieldValues to be searched on. 219 * @param unbounded Whether the results should be bounded or not to a certain max size. 220 * @return A List of search results. 221 * 222 */ 223 protected List<? extends BusinessObject> getSearchResultsHelper(Map<String, String> fieldValues, boolean unbounded) { 224 // remove hidden fields 225 LookupUtils.removeHiddenCriteriaFields(getBusinessObjectClass(), fieldValues); 226 227 searchUsingOnlyPrimaryKeyValues = getLookupService().allPrimaryKeyValuesPresentAndNotWildcard(getBusinessObjectClass(), fieldValues); 228 229 setBackLocation(fieldValues.get(KRADConstants.BACK_LOCATION)); 230 setDocFormKey(fieldValues.get(KRADConstants.DOC_FORM_KEY)); 231 setReferencesToRefresh(fieldValues.get(KRADConstants.REFERENCES_TO_REFRESH)); 232 List searchResults; 233 Map<String,String> nonBlankFieldValues = new HashMap<String, String>(); 234 for (String fieldName : fieldValues.keySet()) { 235 String fieldValue = fieldValues.get(fieldName); 236 if (StringUtils.isNotBlank(fieldValue) ) { 237 if (fieldValue.endsWith(EncryptionService.ENCRYPTION_POST_PREFIX)) { 238 String encryptedValue = StringUtils.removeEnd(fieldValue, EncryptionService.ENCRYPTION_POST_PREFIX); 239 try { 240 if(CoreApiServiceLocator.getEncryptionService().isEnabled()) { 241 fieldValue = getEncryptionService().decrypt(encryptedValue); 242 } 243 } 244 catch (GeneralSecurityException e) { 245 LOG.error("Error decrypting value for business object " + getBusinessObjectService() + " attribute " + fieldName, e); 246 throw new RuntimeException("Error decrypting value for business object " + getBusinessObjectService() + " attribute " + fieldName, e); 247 } 248 } 249 nonBlankFieldValues.put(fieldName, fieldValue); 250 } 251 } 252 253 // If this class is an EBO, just call the module service to get the results 254 if ( ExternalizableBusinessObjectUtils.isExternalizableBusinessObject( getBusinessObjectClass() ) ) { 255 ModuleService eboModuleService = KRADServiceLocatorWeb.getKualiModuleService().getResponsibleModuleService( getBusinessObjectClass() ); 256 BusinessObjectEntry ddEntry = eboModuleService.getExternalizableBusinessObjectDictionaryEntry(getBusinessObjectClass()); 257 Map<String,String> filteredFieldValues = new HashMap<String, String>(); 258 for (String fieldName : nonBlankFieldValues.keySet()) { 259 if (ddEntry.getAttributeNames().contains(fieldName)) { 260 filteredFieldValues.put(fieldName, nonBlankFieldValues.get(fieldName)); 261 } 262 } 263 searchResults = eboModuleService.getExternalizableBusinessObjectsListForLookup(getBusinessObjectClass(), (Map)filteredFieldValues, unbounded); 264 // if any of the properties refer to an embedded EBO, call the EBO lookups first and apply to the local lookup 265 } else if ( hasExternalBusinessObjectProperty( getBusinessObjectClass(), nonBlankFieldValues ) ) { 266 if ( LOG.isDebugEnabled() ) { 267 LOG.debug( "has EBO reference: " + getBusinessObjectClass() ); 268 LOG.debug( "properties: " + nonBlankFieldValues ); 269 } 270 // remove the EBO criteria 271 Map<String,String> nonEboFieldValues = removeExternalizableBusinessObjectFieldValues( getBusinessObjectClass(), nonBlankFieldValues ); 272 if ( LOG.isDebugEnabled() ) { 273 LOG.debug( "Non EBO properties removed: " + nonEboFieldValues ); 274 } 275 // get the list of EBO properties attached to this object 276 List<String> eboPropertyNames = getExternalizableBusinessObjectProperties( getBusinessObjectClass(), nonBlankFieldValues ); 277 if ( LOG.isDebugEnabled() ) { 278 LOG.debug( "EBO properties: " + eboPropertyNames ); 279 } 280 // loop over those properties 281 for ( String eboPropertyName : eboPropertyNames ) { 282 // extract the properties as known to the EBO 283 Map<String,String> eboFieldValues = getExternalizableBusinessObjectFieldValues( eboPropertyName, nonBlankFieldValues ); 284 if ( LOG.isDebugEnabled() ) { 285 LOG.debug( "EBO properties for master EBO property: " + eboPropertyName ); 286 LOG.debug( "properties: " + eboFieldValues ); 287 } 288 // run search against attached EBO's module service 289 ModuleService eboModuleService = KRADServiceLocatorWeb.getKualiModuleService().getResponsibleModuleService( getExternalizableBusinessObjectClass( getBusinessObjectClass(), eboPropertyName) ); 290 // KULRICE-4401 made eboResults an empty list and only filled if service is found. 291 List eboResults = Collections.emptyList(); 292 if (eboModuleService != null) 293 { 294 eboResults = eboModuleService.getExternalizableBusinessObjectsListForLookup( getExternalizableBusinessObjectClass( getBusinessObjectClass(), eboPropertyName), (Map)eboFieldValues, unbounded); 295 } 296 else 297 { 298 LOG.debug( "EBO ModuleService is null: " + eboPropertyName ); 299 } 300 // get the mapping/relationship between the EBO object and it's parent object 301 // use that to adjust the fieldValues 302 303 // get the parent property type 304 Class eboParentClass; 305 String eboParentPropertyName; 306 if ( ObjectUtils.isNestedAttribute( eboPropertyName ) ) { 307 eboParentPropertyName = StringUtils.substringBeforeLast( eboPropertyName, "." ); 308 try { 309 eboParentClass = PropertyUtils.getPropertyType( getBusinessObjectClass().newInstance(), eboParentPropertyName ); 310 } catch ( Exception ex ) { 311 throw new RuntimeException( "Unable to create an instance of the business object class: " + getBusinessObjectClass().getName(), ex ); 312 } 313 } else { 314 eboParentClass = getBusinessObjectClass(); 315 eboParentPropertyName = null; 316 } 317 if ( LOG.isDebugEnabled() ) { 318 LOG.debug( "determined EBO parent class/property name: " + eboParentClass + "/" + eboParentPropertyName ); 319 } 320 // look that up in the DD (BOMDS) 321 // find the appropriate relationship 322 // CHECK THIS: what if eboPropertyName is a nested attribute - need to strip off the eboParentPropertyName if not null 323 RelationshipDefinition rd = getBusinessObjectMetaDataService().getBusinessObjectRelationshipDefinition( eboParentClass, eboPropertyName ); 324 if ( LOG.isDebugEnabled() ) { 325 LOG.debug( "Obtained RelationshipDefinition for " + eboPropertyName ); 326 LOG.debug( rd ); 327 } 328 329 // copy the needed properties (primary only) to the field values 330 // KULRICE-4446 do so only if the relationship definition exists 331 // NOTE: this will work only for single-field PK unless the ORM layer is directly involved 332 // (can't make (field1,field2) in ( (v1,v2),(v3,v4) ) style queries in the lookup framework 333 if ( ObjectUtils.isNotNull(rd)) { 334 if ( rd.getPrimitiveAttributes().size() > 1 ) { 335 throw new RuntimeException( "EBO Links don't work for relationships with multiple-field primary keys." ); 336 } 337 String boProperty = rd.getPrimitiveAttributes().get( 0 ).getSourceName(); 338 String eboProperty = rd.getPrimitiveAttributes().get( 0 ).getTargetName(); 339 StringBuffer boPropertyValue = new StringBuffer(); 340 // loop over the results, making a string that the lookup DAO will convert into an 341 // SQL "IN" clause 342 for ( Object ebo : eboResults ) { 343 if ( boPropertyValue.length() != 0 ) { 344 boPropertyValue.append( SearchOperator.OR.op() ); 345 } 346 try { 347 boPropertyValue.append( PropertyUtils.getProperty( ebo, eboProperty ).toString() ); 348 } catch ( Exception ex ) { 349 LOG.warn( "Unable to get value for " + eboProperty + " on " + ebo ); 350 } 351 } 352 if ( eboParentPropertyName == null ) { 353 // non-nested property containing the EBO 354 nonEboFieldValues.put( boProperty, boPropertyValue.toString() ); 355 } else { 356 // property nested within the main searched-for BO that contains the EBO 357 nonEboFieldValues.put( eboParentPropertyName + "." + boProperty, boPropertyValue.toString() ); 358 } 359 } 360 } 361 if ( LOG.isDebugEnabled() ) { 362 LOG.debug( "Passing these results into the lookup service: " + nonEboFieldValues ); 363 } 364 // add those results as criteria 365 // run the normal search (but with the EBO critieria added) 366 searchResults = (List) getLookupService().findCollectionBySearchHelper(getBusinessObjectClass(), nonEboFieldValues, unbounded); 367 } else { 368 searchResults = (List) getLookupService().findCollectionBySearchHelper(getBusinessObjectClass(), nonBlankFieldValues, unbounded); 369 } 370 371 if (searchResults == null) { 372 searchResults = new ArrayList(); 373 } 374 375 // sort list if default sort column given 376 List defaultSortColumns = getDefaultSortColumns(); 377 if (defaultSortColumns.size() > 0) { 378 Collections.sort(searchResults, new BeanPropertyComparator(defaultSortColumns, true)); 379 } 380 return searchResults; 381 } 382 383 384 /** 385 * @see LookupableHelperService#isSearchUsingOnlyPrimaryKeyValues() 386 */ 387 @Override 388 public boolean isSearchUsingOnlyPrimaryKeyValues() { 389 return searchUsingOnlyPrimaryKeyValues; 390} 391 392 393 /** 394 * 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 395 * 396 * These labels are generated from the DD definitions for the lookup fields 397 * 398 * @return a comma separated list of field attribute names. If no fields found, returns "N/A" 399 * @see LookupableHelperService#isSearchUsingOnlyPrimaryKeyValues() 400 * @see LookupableHelperService#getPrimaryKeyFieldLabels() 401 */ 402 @Override 403 public String getPrimaryKeyFieldLabels() { 404 StringBuilder buf = new StringBuilder(); 405 List<String> primaryKeyFieldNames = KRADServiceLocatorWeb.getLegacyDataAdapter().listPrimaryKeyFieldNames(getBusinessObjectClass()); 406 Iterator<String> pkIter = primaryKeyFieldNames.iterator(); 407 while (pkIter.hasNext()) { 408 String pkFieldName = (String) pkIter.next(); 409 buf.append(getDataDictionaryService().getAttributeLabel(getBusinessObjectClass(), pkFieldName)); 410 if (pkIter.hasNext()) { 411 buf.append(", "); 412 } 413 } 414 return buf.length() == 0 ? KRADConstants.NOT_AVAILABLE_STRING : buf.toString(); 415 } 416 417 418} 419