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 }