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