View Javadoc

1   /**
2    * Copyright 2005-2014 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.lang.StringUtils;
19  import org.apache.ojb.broker.query.Criteria;
20  import org.kuali.rice.coreservice.framework.CoreFrameworkServiceLocator;
21  import org.kuali.rice.core.api.CoreApiServiceLocator;
22  import org.kuali.rice.core.api.encryption.EncryptionService;
23  import org.kuali.rice.core.api.search.SearchOperator;
24  import org.kuali.rice.core.framework.persistence.platform.DatabasePlatform;
25  import org.kuali.rice.krad.bo.ExternalizableBusinessObject;
26  import org.kuali.rice.krad.datadictionary.exception.UnknownBusinessClassAttributeException;
27  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
28  import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
29  import org.kuali.rice.krad.util.ExternalizableBusinessObjectUtils;
30  import org.kuali.rice.krad.util.KRADConstants;
31  import org.kuali.rice.krad.util.KRADPropertyConstants;
32  import org.kuali.rice.krad.util.ObjectUtils;
33  
34  import java.sql.Date;
35  import java.sql.Timestamp;
36  import java.text.ParseException;
37  import java.util.ArrayList;
38  import java.util.Calendar;
39  import java.util.HashMap;
40  import java.util.HashSet;
41  import java.util.List;
42  import java.util.Map;
43  import java.util.Set;
44  
45  /**
46   * Provides static utility methods for use within the lookup framework
47   *
48   * @author Kuali Rice Team (rice.collab@kuali.org)
49   */
50  public class LookupUtils {
51  
52      /**
53       * Uses the DataDictionary to determine whether to force uppercase the value, and if it should, then it does the
54       * uppercase, and returns the upper-cased value.
55       *
56       * @param dataObjectClass Parent DO class that the fieldName is a member of.
57       * @param fieldName Name of the field to be forced to uppercase.
58       * @param fieldValue Value of the field that may be uppercased.
59       * @return The correctly uppercased fieldValue if it should be uppercased, otherwise fieldValue is returned
60       *         unchanged.
61       */
62      public static String forceUppercase(Class<?> dataObjectClass, String fieldName, String fieldValue) {
63          // short-circuit to exit if there isnt enough information to do the forceUppercase
64          if (StringUtils.isBlank(fieldValue)) {
65              return fieldValue;
66          }
67  
68          // parameter validation
69          if (dataObjectClass == null) {
70              throw new IllegalArgumentException("Parameter dataObjectClass passed in with null value.");
71          }
72  
73          if (StringUtils.isBlank(fieldName)) {
74              throw new IllegalArgumentException("Parameter fieldName passed in with empty value.");
75          }
76  
77          if (!KRADServiceLocatorWeb.getDataDictionaryService().isAttributeDefined(dataObjectClass, fieldName)
78                  .booleanValue()) {
79              return fieldValue;
80          }
81  
82          boolean forceUpperCase = false;
83          try {
84              forceUpperCase = KRADServiceLocatorWeb.getDataDictionaryService()
85                      .getAttributeForceUppercase(dataObjectClass, fieldName).booleanValue();
86          } catch (UnknownBusinessClassAttributeException ubae) {
87              // do nothing, don't alter the fieldValue
88          }
89          if (forceUpperCase && !fieldValue.endsWith(EncryptionService.ENCRYPTION_POST_PREFIX)) {
90              return fieldValue.toUpperCase();
91          }
92  
93          return fieldValue;
94      }
95  
96      /**
97       * Uses the DataDictionary to determine whether to force uppercase the values, and if it should, then it does the
98       * uppercase, and returns the upper-cased Map of fieldname/fieldValue pairs.
99       *
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      * @return result set limit
127      */
128     public static Integer getSearchResultsLimit(Class dataObjectClass) {
129         Integer limit = KRADServiceLocatorWeb.getViewDictionaryService().getResultSetLimitForLookup(dataObjectClass);
130         if (limit == null) {
131             limit = getApplicationSearchResultsLimit();
132         }
133 
134         return limit;
135     }
136 
137     /**
138      * Retrieves the default application search limit configured through
139      * a system parameter
140      *
141      * @return default result set limit of the application
142      */
143     public static Integer getApplicationSearchResultsLimit() {
144         String limitString = CoreFrameworkServiceLocator.getParameterService()
145                 .getParameterValueAsString(KRADConstants.KNS_NAMESPACE,
146                         KRADConstants.DetailTypes.LOOKUP_PARM_DETAIL_TYPE,
147                         KRADConstants.SystemGroupParameterNames.LOOKUP_RESULTS_LIMIT);
148         if (limitString != null) {
149             return Integer.valueOf(limitString);
150         }
151 
152         return null;
153     }
154 
155     /**
156      * This method applies the search results limit to the search criteria for this BO
157      *
158      * @param businessObjectClass BO class to search on / get limit for
159      * @param criteria search criteria
160      * @param platform database platform
161      */
162     public static void applySearchResultsLimit(Class businessObjectClass, Criteria criteria,
163             DatabasePlatform platform) {
164         Integer limit = getSearchResultsLimit(businessObjectClass);
165         if (limit != null) {
166             platform.applyLimit(limit, criteria);
167         }
168     }
169 
170     /**
171      * Applies the search results limit to the search criteria for this BO (JPA)
172      *
173      * @param businessObjectClass BO class to search on / get limit for
174      * @param criteria search criteria
175      */
176     public static void applySearchResultsLimit(Class businessObjectClass,
177             org.kuali.rice.core.framework.persistence.jpa.criteria.Criteria criteria) {
178         Integer limit = getSearchResultsLimit(businessObjectClass);
179         if (limit != null) {
180             criteria.setSearchLimit(limit);
181         }
182     }
183 
184     /**
185      * Determines what Timestamp should be used for active queries on effective dated records. Determination made as
186      * follows:
187      *
188      * <ul>
189      * <li>Use activeAsOfDate value from search values Map if value is not empty</li>
190      * <li>If search value given, try to convert to sql date, if conversion fails, try to convert to Timestamp</li>
191      * <li>If search value empty, use current Date</li>
192      * <li>If Timestamp value not given, create Timestamp from given Date setting the time as 1 second before midnight
193      * </ul>
194      *
195      * @param searchValues - Map containing search key/value pairs
196      * @return Timestamp to be used for active criteria
197      */
198     public static Timestamp getActiveDateTimestampForCriteria(Map searchValues) {
199         Date activeDate = CoreApiServiceLocator.getDateTimeService().getCurrentSqlDate();
200         Timestamp activeTimestamp = null;
201         if (searchValues.containsKey(KRADPropertyConstants.ACTIVE_AS_OF_DATE)) {
202             String activeAsOfDate = (String) searchValues.get(KRADPropertyConstants.ACTIVE_AS_OF_DATE);
203             if (StringUtils.isNotBlank(activeAsOfDate)) {
204                 try {
205                     activeDate = CoreApiServiceLocator.getDateTimeService()
206                             .convertToSqlDate(ObjectUtils.clean(activeAsOfDate));
207                 } catch (ParseException e) {
208                     // try to parse as timestamp
209                     try {
210                         activeTimestamp = CoreApiServiceLocator.getDateTimeService()
211                                 .convertToSqlTimestamp(ObjectUtils.clean(activeAsOfDate));
212                     } catch (ParseException e1) {
213                         throw new RuntimeException("Unable to convert date: " + ObjectUtils.clean(activeAsOfDate));
214                     }
215                 }
216             }
217         }
218 
219         // if timestamp not given set to 1 second before midnight on the given date
220         if (activeTimestamp == null) {
221             Calendar cal = Calendar.getInstance();
222             cal.setTime(activeDate);
223             cal.set(Calendar.HOUR, cal.getMaximum(Calendar.HOUR));
224             cal.set(Calendar.MINUTE, cal.getMaximum(Calendar.MINUTE));
225             cal.set(Calendar.SECOND, cal.getMaximum(Calendar.SECOND));
226 
227             activeTimestamp = new Timestamp(cal.getTime().getTime());
228         }
229 
230         return activeTimestamp;
231     }
232 
233     /**
234      * Changes from/to dates into the range operators the lookupable dao expects ("..",">" etc) this method modifies
235      * the passed in map and returns an updated search criteria map
236      *
237      * @param searchCriteria - map of criteria currently set for which the date criteria will be adjusted
238      * @return map updated search criteria
239      */
240     public static Map<String, String> preprocessDateFields(Map<String, String> searchCriteria) {
241         Map<String, String> fieldsToUpdate = new HashMap<String, String>();
242         Map<String, String> searchCriteriaUpdated = new HashMap<String, String>(searchCriteria);
243         Set<String> fieldsForLookup = searchCriteria.keySet();
244         for (String propName : fieldsForLookup) {
245             if (propName.startsWith(KRADConstants.LOOKUP_RANGE_LOWER_BOUND_PROPERTY_PREFIX)) {
246                 String from_DateValue = searchCriteria.get(propName);
247                 String dateFieldName =
248                         StringUtils.remove(propName, KRADConstants.LOOKUP_RANGE_LOWER_BOUND_PROPERTY_PREFIX);
249                 String to_DateValue = searchCriteria.get(dateFieldName);
250                 String newPropValue = to_DateValue;// maybe clean above with
251                 // ObjectUtils.clean(propertyValue)
252                 if (StringUtils.isNotEmpty(from_DateValue) && StringUtils.isNotEmpty(to_DateValue)) {
253                     newPropValue = from_DateValue + SearchOperator.BETWEEN + to_DateValue;
254                 } else if (StringUtils.isNotEmpty(from_DateValue) && StringUtils.isEmpty(to_DateValue)) {
255                     newPropValue = SearchOperator.GREATER_THAN_EQUAL.op() + from_DateValue;
256                 } else if (StringUtils.isNotEmpty(to_DateValue) && StringUtils.isEmpty(from_DateValue)) {
257                     newPropValue = SearchOperator.LESS_THAN_EQUAL.op() + to_DateValue;
258                 } // could optionally continue on else here
259 
260                 fieldsToUpdate.put(dateFieldName, newPropValue);
261             }
262         }
263 
264         // update lookup values from found date values to update
265         Set<String> keysToUpdate = fieldsToUpdate.keySet();
266         for (String updateKey : keysToUpdate) {
267             searchCriteriaUpdated.put(updateKey, fieldsToUpdate.get(updateKey));
268         }
269 
270         return searchCriteriaUpdated;
271     }
272 
273     /**
274      * Checks whether any of the fieldValues being passed refer to a property within an ExternalizableBusinessObject.
275      *
276      * @param boClass business object class of the lookup
277      * @param fieldValues map of the lookup criteria values
278      * @return true if externalizable buiness object are contained, false otherwise
279      * @throws IllegalAccessException
280      * @throws InstantiationException
281      */
282     public static boolean hasExternalBusinessObjectProperty(Class<?> boClass,
283             Map<String, String> fieldValues) throws IllegalAccessException, InstantiationException {
284         Object sampleBo = boClass.newInstance();
285         for (String key : fieldValues.keySet()) {
286             if (isExternalBusinessObjectProperty(sampleBo, key)) {
287                 return true;
288             }
289         }
290 
291         return false;
292     }
293 
294     /**
295      * Check whether the given property represents a property within an EBO starting with the sampleBo object given.
296      * This is used to determine if a criteria needs to be applied to the EBO first,
297      * before sending to the normal lookup DAO.
298      *
299      * @param sampleBo business object of the property to be tested
300      * @param propertyName property name to be tested
301      * @return true if the property is within an externalizable business object.
302      */
303     public static boolean isExternalBusinessObjectProperty(Object sampleBo, String propertyName) {
304         if (propertyName.indexOf(".") > 0 && !StringUtils.contains(propertyName, "add.")) {
305             Class<?> propertyClass =
306                     ObjectPropertyUtils.getPropertyType(sampleBo, StringUtils.substringBeforeLast(propertyName, "."));
307             if (propertyClass != null) {
308                 return ExternalizableBusinessObjectUtils.isExternalizableBusinessObjectInterface(propertyClass);
309             }
310         }
311 
312         return false;
313     }
314 
315     /**
316      * Returns a map stripped of any properties which refer to ExternalizableBusinessObjects. These values may not be
317      * passed into the lookup service, since the objects they refer to are not in the
318      * local database.
319      *
320      * @param boClass business object class of the lookup
321      * @param fieldValues map of lookup criteria from which to remove the externalizable business objects
322      * @return map of lookup criteria without externalizable business objects
323      */
324     public static Map<String, String> removeExternalizableBusinessObjectFieldValues(Class<?> boClass,
325             Map<String, String> fieldValues) throws IllegalAccessException, InstantiationException {
326         Map<String, String> eboFieldValues = new HashMap<String, String>();
327         Object sampleBo = boClass.newInstance();
328         for (String key : fieldValues.keySet()) {
329             if (!isExternalBusinessObjectProperty(sampleBo, key)) {
330                 eboFieldValues.put(key, fieldValues.get(key));
331             }
332         }
333 
334         return eboFieldValues;
335     }
336 
337     /**
338      * Return the EBO fieldValue entries explicitly for the given eboPropertyName. (I.e., any properties with the given
339      * property name as a prefix.
340      *
341      * @param eboPropertyName the externalizable business object property name to retrieve
342      * @param fieldValues map of lookup criteria
343      * return map of lookup criteria for the given eboPropertyName
344      */
345     public static Map<String, String> getExternalizableBusinessObjectFieldValues(String eboPropertyName,
346             Map<String, String> fieldValues) {
347         Map<String, String> eboFieldValues = new HashMap<String, String>();
348         for (String key : fieldValues.keySet()) {
349             if (key.startsWith(eboPropertyName + ".")) {
350                 eboFieldValues.put(StringUtils.substringAfterLast(key, "."), fieldValues.get(key));
351             }
352         }
353 
354         return eboFieldValues;
355     }
356 
357     /**
358      * Get the complete list of all properties referenced in the fieldValues that are ExternalizableBusinessObjects.
359      *
360      * <p>
361      * This is a list of the EBO object references themselves, not of the properties within them.
362      * </p>
363      *
364      * @param boClass business object class of the lookup
365      * @param fieldValues map of lookup criteria from which to return the externalizable business objects
366      * @return map of lookup criteria that are externalizable business objects
367      * @throws IllegalAccessException
368      * @throws InstantiationException
369      */
370     public static List<String> getExternalizableBusinessObjectProperties(Class<?> boClass,
371             Map<String, String> fieldValues) throws IllegalAccessException, InstantiationException {
372         Set<String> eboPropertyNames = new HashSet<String>();
373 
374         Object sampleBo = boClass.newInstance();
375         for (String key : fieldValues.keySet()) {
376             if (isExternalBusinessObjectProperty(sampleBo, key)) {
377                 eboPropertyNames.add(StringUtils.substringBeforeLast(key, "."));
378             }
379         }
380 
381         return new ArrayList<String>(eboPropertyNames);
382     }
383 
384     /**
385      * Given an property on the main BO class, return the defined type of the ExternalizableBusinessObject. This will
386      * be used by other code to determine the correct module service to call for the lookup.
387      *
388      * @param boClass business object class of the lookup
389      * @param propertyName property of which the externalizable business object type is to be determined
390      * @return externalizable business object type
391      * @throws IllegalAccessException
392      * @throws InstantiationException
393      */
394     public static Class<? extends ExternalizableBusinessObject> getExternalizableBusinessObjectClass(Class<?> boClass,
395             String propertyName) throws IllegalAccessException, InstantiationException {
396         return (Class<? extends ExternalizableBusinessObject>) ObjectPropertyUtils
397                 .getPropertyType(boClass.newInstance(), StringUtils.substringBeforeLast(propertyName, "."));
398     }
399 
400 }