001 /** 002 * Copyright 2005-2012 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 */ 016 package org.kuali.rice.krad.lookup; 017 018 import org.apache.commons.lang.StringUtils; 019 import org.apache.ojb.broker.query.Criteria; 020 import org.kuali.rice.coreservice.framework.CoreFrameworkServiceLocator; 021 import org.kuali.rice.core.api.CoreApiServiceLocator; 022 import org.kuali.rice.core.api.encryption.EncryptionService; 023 import org.kuali.rice.core.api.search.SearchOperator; 024 import org.kuali.rice.core.framework.persistence.platform.DatabasePlatform; 025 import org.kuali.rice.krad.bo.ExternalizableBusinessObject; 026 import org.kuali.rice.krad.datadictionary.exception.UnknownBusinessClassAttributeException; 027 import org.kuali.rice.krad.service.KRADServiceLocatorWeb; 028 import org.kuali.rice.krad.uif.util.ObjectPropertyUtils; 029 import org.kuali.rice.krad.util.ExternalizableBusinessObjectUtils; 030 import org.kuali.rice.krad.util.KRADConstants; 031 import org.kuali.rice.krad.util.KRADPropertyConstants; 032 import org.kuali.rice.krad.util.ObjectUtils; 033 034 import java.sql.Date; 035 import java.sql.Timestamp; 036 import java.text.ParseException; 037 import java.util.ArrayList; 038 import java.util.Calendar; 039 import java.util.HashMap; 040 import java.util.HashSet; 041 import java.util.List; 042 import java.util.Map; 043 import java.util.Set; 044 045 /** 046 * Provides static utility methods for use within the lookup framework 047 * 048 * @author Kuali Rice Team (rice.collab@kuali.org) 049 */ 050 public class LookupUtils { 051 052 /** 053 * Uses the DataDictionary to determine whether to force uppercase the value, and if it should, then it does the 054 * uppercase, and returns the upper-cased value. 055 * 056 * @param dataObjectClass Parent DO class that the fieldName is a member of. 057 * @param fieldName Name of the field to be forced to uppercase. 058 * @param fieldValue Value of the field that may be uppercased. 059 * @return The correctly uppercased fieldValue if it should be uppercased, otherwise fieldValue is returned 060 * unchanged. 061 */ 062 public static String forceUppercase(Class<?> dataObjectClass, String fieldName, String fieldValue) { 063 // short-circuit to exit if there isnt enough information to do the forceUppercase 064 if (StringUtils.isBlank(fieldValue)) { 065 return fieldValue; 066 } 067 068 // parameter validation 069 if (dataObjectClass == null) { 070 throw new IllegalArgumentException("Parameter dataObjectClass passed in with null value."); 071 } 072 073 if (StringUtils.isBlank(fieldName)) { 074 throw new IllegalArgumentException("Parameter fieldName passed in with empty value."); 075 } 076 077 if (!KRADServiceLocatorWeb.getDataDictionaryService().isAttributeDefined(dataObjectClass, fieldName) 078 .booleanValue()) { 079 return fieldValue; 080 } 081 082 boolean forceUpperCase = false; 083 try { 084 forceUpperCase = KRADServiceLocatorWeb.getDataDictionaryService() 085 .getAttributeForceUppercase(dataObjectClass, fieldName).booleanValue(); 086 } catch (UnknownBusinessClassAttributeException ubae) { 087 // do nothing, don't alter the fieldValue 088 } 089 if (forceUpperCase && !fieldValue.endsWith(EncryptionService.ENCRYPTION_POST_PREFIX)) { 090 return fieldValue.toUpperCase(); 091 } 092 093 return fieldValue; 094 } 095 096 /** 097 * Uses the DataDictionary to determine whether to force uppercase the values, and if it should, then it does the 098 * uppercase, and returns the upper-cased Map of fieldname/fieldValue pairs. 099 * 100 * @param dataObjectClass Parent DO class that the fieldName is a member of. 101 * @param fieldValues A Map<String,String> where the key is the fieldName and the value is the fieldValue. 102 * @return The same Map is returned, with the appropriate values uppercased (if any). 103 */ 104 public static Map<String, String> forceUppercase(Class<?> dataObjectClass, Map<String, String> fieldValues) { 105 if (dataObjectClass == null) { 106 throw new IllegalArgumentException("Parameter boClass passed in with null value."); 107 } 108 109 if (fieldValues == null) { 110 throw new IllegalArgumentException("Parameter fieldValues passed in with null value."); 111 } 112 113 for (String fieldName : fieldValues.keySet()) { 114 fieldValues.put(fieldName, forceUppercase(dataObjectClass, fieldName, (String) fieldValues.get(fieldName))); 115 } 116 117 return fieldValues; 118 } 119 120 /** 121 * Parses and returns the lookup result set limit, checking first for the limit 122 * for the class being looked up, and then the global application limit if there isn't a limit 123 * specific to this data object class 124 * 125 * @param dataObjectClass - class to get limit for 126 */ 127 public static Integer getSearchResultsLimit(Class dataObjectClass) { 128 Integer limit = KRADServiceLocatorWeb.getViewDictionaryService().getResultSetLimitForLookup(dataObjectClass); 129 if (limit == null) { 130 limit = getApplicationSearchResultsLimit(); 131 } 132 133 return limit; 134 } 135 136 /** 137 * Retrieves the default application search limit configured through 138 * a system parameter 139 */ 140 public static Integer getApplicationSearchResultsLimit() { 141 String limitString = CoreFrameworkServiceLocator.getParameterService() 142 .getParameterValueAsString(KRADConstants.KRAD_NAMESPACE, 143 KRADConstants.DetailTypes.LOOKUP_PARM_DETAIL_TYPE, 144 KRADConstants.SystemGroupParameterNames.LOOKUP_RESULTS_LIMIT); 145 if (limitString != null) { 146 return Integer.valueOf(limitString); 147 } 148 149 return null; 150 } 151 152 /** 153 * This method applies the search results limit to the search criteria for this BO 154 * 155 * @param businessObjectClass BO class to search on / get limit for 156 * @param criteria search criteria 157 * @param platform database platform 158 */ 159 public static void applySearchResultsLimit(Class businessObjectClass, Criteria criteria, 160 DatabasePlatform platform) { 161 Integer limit = getSearchResultsLimit(businessObjectClass); 162 if (limit != null) { 163 platform.applyLimit(limit, criteria); 164 } 165 } 166 167 /** 168 * Applies the search results limit to the search criteria for this BO (JPA) 169 * 170 * @param businessObjectClass BO class to search on / get limit for 171 * @param criteria search criteria 172 */ 173 public static void applySearchResultsLimit(Class businessObjectClass, 174 org.kuali.rice.core.framework.persistence.jpa.criteria.Criteria criteria) { 175 Integer limit = getSearchResultsLimit(businessObjectClass); 176 if (limit != null) { 177 criteria.setSearchLimit(limit); 178 } 179 } 180 181 /** 182 * Determines what Timestamp should be used for active queries on effective dated records. Determination made as 183 * follows: 184 * 185 * <ul> 186 * <li>Use activeAsOfDate value from search values Map if value is not empty</li> 187 * <li>If search value given, try to convert to sql date, if conversion fails, try to convert to Timestamp</li> 188 * <li>If search value empty, use current Date</li> 189 * <li>If Timestamp value not given, create Timestamp from given Date setting the time as 1 second before midnight 190 * </ul> 191 * 192 * @param searchValues - Map containing search key/value pairs 193 * @return Timestamp to be used for active criteria 194 */ 195 public static Timestamp getActiveDateTimestampForCriteria(Map searchValues) { 196 Date activeDate = CoreApiServiceLocator.getDateTimeService().getCurrentSqlDate(); 197 Timestamp activeTimestamp = null; 198 if (searchValues.containsKey(KRADPropertyConstants.ACTIVE_AS_OF_DATE)) { 199 String activeAsOfDate = (String) searchValues.get(KRADPropertyConstants.ACTIVE_AS_OF_DATE); 200 if (StringUtils.isNotBlank(activeAsOfDate)) { 201 try { 202 activeDate = CoreApiServiceLocator.getDateTimeService() 203 .convertToSqlDate(ObjectUtils.clean(activeAsOfDate)); 204 } catch (ParseException e) { 205 // try to parse as timestamp 206 try { 207 activeTimestamp = CoreApiServiceLocator.getDateTimeService() 208 .convertToSqlTimestamp(ObjectUtils.clean(activeAsOfDate)); 209 } catch (ParseException e1) { 210 throw new RuntimeException("Unable to convert date: " + ObjectUtils.clean(activeAsOfDate)); 211 } 212 } 213 } 214 } 215 216 // if timestamp not given set to 1 second before midnight on the given date 217 if (activeTimestamp == null) { 218 Calendar cal = Calendar.getInstance(); 219 cal.setTime(activeDate); 220 cal.set(Calendar.HOUR, cal.getMaximum(Calendar.HOUR)); 221 cal.set(Calendar.MINUTE, cal.getMaximum(Calendar.MINUTE)); 222 cal.set(Calendar.SECOND, cal.getMaximum(Calendar.SECOND)); 223 224 activeTimestamp = new Timestamp(cal.getTime().getTime()); 225 } 226 227 return activeTimestamp; 228 } 229 230 /** 231 * Changes from/to dates into the range operators the lookupable dao expects ("..",">" etc) this method modifies 232 * the 233 * passed in map and returns a list containing only the modified fields 234 * 235 * @param searchCriteria - map of criteria currently set for which the date criteria will be adjusted 236 */ 237 public static Map<String, String> preprocessDateFields(Map<String, String> searchCriteria) { 238 Map<String, String> fieldsToUpdate = new HashMap<String, String>(); 239 Set<String> fieldsForLookup = searchCriteria.keySet(); 240 for (String propName : fieldsForLookup) { 241 if (propName.startsWith(KRADConstants.LOOKUP_RANGE_LOWER_BOUND_PROPERTY_PREFIX)) { 242 String from_DateValue = searchCriteria.get(propName); 243 String dateFieldName = 244 StringUtils.remove(propName, KRADConstants.LOOKUP_RANGE_LOWER_BOUND_PROPERTY_PREFIX); 245 String to_DateValue = searchCriteria.get(dateFieldName); 246 String newPropValue = to_DateValue;// maybe clean above with 247 // ObjectUtils.clean(propertyValue) 248 if (StringUtils.isNotEmpty(from_DateValue) && StringUtils.isNotEmpty(to_DateValue)) { 249 newPropValue = from_DateValue + SearchOperator.BETWEEN + to_DateValue; 250 } else if (StringUtils.isNotEmpty(from_DateValue) && StringUtils.isEmpty(to_DateValue)) { 251 newPropValue = SearchOperator.GREATER_THAN_EQUAL.op() + from_DateValue; 252 } else if (StringUtils.isNotEmpty(to_DateValue) && StringUtils.isEmpty(from_DateValue)) { 253 newPropValue = SearchOperator.LESS_THAN_EQUAL.op() + to_DateValue; 254 } // could optionally continue on else here 255 256 fieldsToUpdate.put(dateFieldName, newPropValue); 257 } 258 } 259 260 // update lookup values from found date values to update 261 Set<String> keysToUpdate = fieldsToUpdate.keySet(); 262 for (String updateKey : keysToUpdate) { 263 searchCriteria.put(updateKey, fieldsToUpdate.get(updateKey)); 264 } 265 266 return fieldsToUpdate; 267 } 268 269 /** 270 * Checks whether any of the fieldValues being passed refer to a property within an ExternalizableBusinessObject. 271 */ 272 public static boolean hasExternalBusinessObjectProperty(Class<?> boClass, 273 Map<String, String> fieldValues) throws IllegalAccessException, InstantiationException { 274 Object sampleBo = boClass.newInstance(); 275 for (String key : fieldValues.keySet()) { 276 if (isExternalBusinessObjectProperty(sampleBo, key)) { 277 return true; 278 } 279 } 280 281 return false; 282 } 283 284 /** 285 * Check whether the given property represents a property within an EBO starting with the sampleBo object given. 286 * This is used to determine if a criteria needs to be applied to the EBO first, 287 * before sending to the normal lookup DAO. 288 */ 289 public static boolean isExternalBusinessObjectProperty(Object sampleBo, String propertyName) { 290 if (propertyName.indexOf(".") > 0 && !StringUtils.contains(propertyName, "add.")) { 291 Class<?> propertyClass = 292 ObjectPropertyUtils.getPropertyType(sampleBo, StringUtils.substringBeforeLast(propertyName, ".")); 293 if (propertyClass != null) { 294 return ExternalizableBusinessObjectUtils.isExternalizableBusinessObjectInterface(propertyClass); 295 } 296 } 297 298 return false; 299 } 300 301 /** 302 * Returns a map stripped of any properties which refer to ExternalizableBusinessObjects. These values may not be 303 * passed into the lookup service, since the objects they refer to are not in the 304 * local database. 305 */ 306 public static Map<String, String> removeExternalizableBusinessObjectFieldValues(Class<?> boClass, 307 Map<String, String> fieldValues) throws IllegalAccessException, InstantiationException { 308 Map<String, String> eboFieldValues = new HashMap<String, String>(); 309 Object sampleBo = boClass.newInstance(); 310 for (String key : fieldValues.keySet()) { 311 if (!isExternalBusinessObjectProperty(sampleBo, key)) { 312 eboFieldValues.put(key, fieldValues.get(key)); 313 } 314 } 315 316 return eboFieldValues; 317 } 318 319 /** 320 * Return the EBO fieldValue entries explicitly for the given eboPropertyName. (I.e., any properties with the given 321 * property name as a prefix. 322 */ 323 public static Map<String, String> getExternalizableBusinessObjectFieldValues(String eboPropertyName, 324 Map<String, String> fieldValues) { 325 Map<String, String> eboFieldValues = new HashMap<String, String>(); 326 for (String key : fieldValues.keySet()) { 327 if (key.startsWith(eboPropertyName + ".")) { 328 eboFieldValues.put(StringUtils.substringAfterLast(key, "."), fieldValues.get(key)); 329 } 330 } 331 332 return eboFieldValues; 333 } 334 335 /** 336 * Get the complete list of all properties referenced in the fieldValues that are ExternalizableBusinessObjects. 337 * 338 * This is a list of the EBO object references themselves, not of the properties within them. 339 */ 340 public static List<String> getExternalizableBusinessObjectProperties(Class<?> boClass, 341 Map<String, String> fieldValues) throws IllegalAccessException, InstantiationException { 342 Set<String> eboPropertyNames = new HashSet<String>(); 343 344 Object sampleBo = boClass.newInstance(); 345 for (String key : fieldValues.keySet()) { 346 if (isExternalBusinessObjectProperty(sampleBo, key)) { 347 eboPropertyNames.add(StringUtils.substringBeforeLast(key, ".")); 348 } 349 } 350 351 return new ArrayList<String>(eboPropertyNames); 352 } 353 354 /** 355 * Given an property on the main BO class, return the defined type of the ExternalizableBusinessObject. This will 356 * be used by other code to determine the correct module service to call for the lookup. 357 * 358 * @param boClass 359 * @param propertyName 360 * @return 361 */ 362 public static Class<? extends ExternalizableBusinessObject> getExternalizableBusinessObjectClass(Class<?> boClass, 363 String propertyName) throws IllegalAccessException, InstantiationException { 364 return (Class<? extends ExternalizableBusinessObject>) ObjectPropertyUtils 365 .getPropertyType(boClass.newInstance(), StringUtils.substringBeforeLast(propertyName, ".")); 366 } 367 368 }