View Javadoc

1   /**
2    * Copyright 2005-2013 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.kns.service.impl;
17  
18  import org.apache.commons.beanutils.PropertyUtils;
19  import org.apache.commons.lang.StringUtils;
20  import org.kuali.rice.core.api.CoreApiServiceLocator;
21  import org.kuali.rice.core.api.util.RiceKeyConstants;
22  import org.kuali.rice.core.api.util.type.TypeUtils;
23  import org.kuali.rice.core.framework.persistence.jdbc.sql.SQLUtils;
24  import org.kuali.rice.core.web.format.DateFormatter;
25  import org.kuali.rice.kns.datadictionary.MaintainableFieldDefinition;
26  import org.kuali.rice.kns.datadictionary.MaintainableItemDefinition;
27  import org.kuali.rice.kns.datadictionary.MaintainableSectionDefinition;
28  import org.kuali.rice.kns.datadictionary.MaintenanceDocumentEntry;
29  import org.kuali.rice.kns.service.DictionaryValidationService;
30  import org.kuali.rice.kns.service.KNSServiceLocator;
31  import org.kuali.rice.krad.bo.BusinessObject;
32  import org.kuali.rice.krad.datadictionary.control.ControlDefinition;
33  import org.kuali.rice.krad.document.Document;
34  import org.kuali.rice.krad.util.GlobalVariables;
35  import org.kuali.rice.krad.util.KRADConstants;
36  import org.kuali.rice.krad.util.ObjectUtils;
37  
38  import java.beans.PropertyDescriptor;
39  import java.lang.reflect.Method;
40  import java.math.BigDecimal;
41  import java.util.List;
42  import java.util.regex.Pattern;
43  
44  /**
45   * @author Kuali Rice Team (rice.collab@kuali.org)
46   */
47  @Deprecated
48  public class DictionaryValidationServiceImpl extends org.kuali.rice.krad.service.impl.DictionaryValidationServiceImpl implements DictionaryValidationService {
49      private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(
50              DictionaryValidationServiceImpl.class);
51  
52      /**
53       * @see org.kuali.rice.krad.service.DictionaryValidationService#validateDocumentAndUpdatableReferencesRecursively(org.kuali.rice.krad.document.Document, int, boolean, boolean)
54       * @deprecated since 2.1
55       */
56      @Override
57      @Deprecated
58      public void validateDocumentAndUpdatableReferencesRecursively(Document document, int maxDepth,
59              boolean validateRequired, boolean chompLastLetterSFromCollectionName) {
60          // Use the KNS validation code here -- this overrides the behavior in the krad version which calls validate(...)
61          validateBusinessObject(document, validateRequired);
62  
63          if (maxDepth > 0) {
64              validateUpdatabableReferencesRecursively(document, maxDepth - 1, validateRequired,
65                      chompLastLetterSFromCollectionName, newIdentitySet());
66          }
67      }
68  
69      /**
70       * @see org.kuali.rice.kns.service.DictionaryValidationService#validateDocumentRecursively(org.kuali.rice.krad.document.Document, int)
71       * @deprecated since 2.0
72       */
73      @Deprecated
74      @Override
75      public void validateDocumentRecursively(Document document, int depth) {
76          // validate primitives of document
77          validateDocument(document);
78  
79          // call method to recursively find business objects and validate
80          validateBusinessObjectsFromDescriptors(document, PropertyUtils.getPropertyDescriptors(document.getClass()),
81                  depth);
82      }
83  
84      /**
85       * @see org.kuali.rice.kns.service.DictionaryValidationService#validateDocument(org.kuali.rice.krad.document.Document)
86       * @param document - document to validate
87       * @deprecated since 2.1.2
88       */
89      @Deprecated
90      @Override
91      public void validateDocument(Document document) {
92          String documentEntryName = document.getDocumentHeader().getWorkflowDocument().getDocumentTypeName();
93  
94          validatePrimitivesFromDescriptors(documentEntryName, document, PropertyUtils.getPropertyDescriptors(document.getClass()), "", true);
95      }
96  
97      @Override
98      @Deprecated
99      public void validateBusinessObject(BusinessObject businessObject) {
100         validateBusinessObject(businessObject, true);
101     }
102 
103     @Override
104     @Deprecated
105     public void validateBusinessObject(BusinessObject businessObject, boolean validateRequired) {
106         if (ObjectUtils.isNull(businessObject)) {
107             return;
108         }
109         try {
110             // validate the primitive attributes of the bo
111             validatePrimitivesFromDescriptors(businessObject.getClass().getName(), businessObject,
112                     PropertyUtils.getPropertyDescriptors(businessObject.getClass()), "", validateRequired);
113         } catch (RuntimeException e) {
114             LOG.error(String.format("Exception while validating %s", businessObject.getClass().getName()), e);
115             throw e;
116         }
117     }
118 
119     /**
120      * @deprecated since 1.1
121      */
122     @Deprecated
123     @Override
124     public void validateBusinessObjectOnMaintenanceDocument(BusinessObject businessObject, String docTypeName) {
125         MaintenanceDocumentEntry entry =
126                 KNSServiceLocator.getMaintenanceDocumentDictionaryService().getMaintenanceDocumentEntry(docTypeName);
127         for (MaintainableSectionDefinition sectionDefinition : entry.getMaintainableSections()) {
128             validateBusinessObjectOnMaintenanceDocumentHelper(businessObject, sectionDefinition.getMaintainableItems(),
129                     "");
130         }
131     }
132 
133     protected void validateBusinessObjectOnMaintenanceDocumentHelper(BusinessObject businessObject,
134             List<? extends MaintainableItemDefinition> itemDefinitions, String errorPrefix) {
135         for (MaintainableItemDefinition itemDefinition : itemDefinitions) {
136             if (itemDefinition instanceof MaintainableFieldDefinition) {
137                 if (getDataDictionaryService().isAttributeDefined(businessObject.getClass(),
138                         itemDefinition.getName())) {
139                     Object value = ObjectUtils.getPropertyValue(businessObject, itemDefinition.getName());
140                     if (value != null && StringUtils.isNotBlank(value.toString())) {
141                         Class propertyType = ObjectUtils.getPropertyType(businessObject, itemDefinition.getName(),
142                                 persistenceStructureService);
143                         if (TypeUtils.isStringClass(propertyType) ||
144                                 TypeUtils.isIntegralClass(propertyType) ||
145                                 TypeUtils.isDecimalClass(propertyType) ||
146                                 TypeUtils.isTemporalClass(propertyType)) {
147                             // check value format against dictionary
148                             if (!TypeUtils.isTemporalClass(propertyType)) {
149                                 validateAttributeFormat(businessObject.getClass().getName(), itemDefinition.getName(),
150                                         value.toString(), errorPrefix + itemDefinition.getName());
151                             }
152                         }
153                     }
154                 }
155             }
156         }
157     }
158 
159     /**
160      * iterates through property descriptors looking for primitives types, calls validate format and required check
161      *
162      * @param entryName
163      * @param object
164      * @param propertyDescriptors
165      * @param errorPrefix
166      */
167     @Deprecated
168     protected void validatePrimitivesFromDescriptors(String entryName, Object object,
169             PropertyDescriptor[] propertyDescriptors, String errorPrefix, boolean validateRequired) {
170         for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
171             validatePrimitiveFromDescriptor(entryName, object, propertyDescriptor, errorPrefix, validateRequired);
172         }
173     }
174 
175     /**
176      * calls validate format and required check for the given propertyDescriptor
177      *
178      * @param entryName
179      * @param object
180      * @param propertyDescriptor
181      * @param errorPrefix
182      */
183     @Override
184     @Deprecated
185     public void validatePrimitiveFromDescriptor(String entryName, Object object, PropertyDescriptor propertyDescriptor,
186             String errorPrefix, boolean validateRequired) {
187         // validate the primitive attributes if defined in the dictionary
188         if (null != propertyDescriptor && getDataDictionaryService().isAttributeDefined(entryName,
189                 propertyDescriptor.getName())) {
190             Object value = ObjectUtils.getPropertyValue(object, propertyDescriptor.getName());
191             Class propertyType = propertyDescriptor.getPropertyType();
192 
193             if (TypeUtils.isStringClass(propertyType) ||
194                     TypeUtils.isIntegralClass(propertyType) ||
195                     TypeUtils.isDecimalClass(propertyType) ||
196                     TypeUtils.isTemporalClass(propertyType)) {
197 
198                 // check value format against dictionary
199                 if (value != null && StringUtils.isNotBlank(value.toString())) {
200                     if (!TypeUtils.isTemporalClass(propertyType)) {
201                         validateAttributeFormat(entryName, propertyDescriptor.getName(), value.toString(),
202                                 errorPrefix + propertyDescriptor.getName());
203                     }
204                 } else if (validateRequired) {
205                     validateAttributeRequired(entryName, propertyDescriptor.getName(), value, Boolean.FALSE,
206                             errorPrefix + propertyDescriptor.getName());
207                 }
208             }
209         }
210     }
211 
212     /**
213      * @see org.kuali.rice.kns.service.DictionaryValidationService#validateAttributeFormat(String, String, String, String)
214      *      objectClassName is the docTypeName
215      * @deprecated since 1.1
216      */
217     @Override
218     @Deprecated
219     public void validateAttributeFormat(String objectClassName, String attributeName, String attributeInValue,
220             String errorKey) {
221         // Retrieve the field's data type, or set to the string data type if an exception occurs when retrieving the class or the DD entry.
222         String attributeDataType = null;
223         try {
224             attributeDataType = getWorkflowAttributePropertyResolutionService().determineFieldDataType(
225                     (Class<? extends BusinessObject>) Class.forName(
226                             getDataDictionaryService().getDataDictionary().getDictionaryObjectEntry(objectClassName)
227                                     .getFullClassName()), attributeName);
228         } catch (ClassNotFoundException e) {
229             attributeDataType = KRADConstants.DATA_TYPE_STRING;
230         } catch (NullPointerException e) {
231             attributeDataType = KRADConstants.DATA_TYPE_STRING;
232         }
233 
234         validateAttributeFormat(objectClassName, attributeName, attributeInValue, attributeDataType, errorKey);
235     }
236 
237     /**
238      * The attributeDataType parameter should be one of the data types specified by the SearchableAttribute
239      * interface; will default to DATA_TYPE_STRING if a data type other than the ones from SearchableAttribute
240      * is specified.
241      *
242      * @deprecated since 1.1
243      */
244     @Override
245     @Deprecated
246     public void validateAttributeFormat(String objectClassName, String attributeName, String attributeInValue,
247             String attributeDataType, String errorKey) {
248         boolean checkDateBounds = false; // this is used so we can check date bounds
249         Class<?> formatterClass = null;
250 
251         if (LOG.isDebugEnabled()) {
252             LOG.debug("(bo, attributeName, attributeValue) = (" + objectClassName + "," + attributeName + "," +
253                     attributeInValue + ")");
254         }
255 
256         /*
257         *  This will return a list of searchable attributes. so if the value is
258         *  12/07/09 .. 12/08/09 it will return [12/07/09,12/08/09]
259         */
260 
261         final List<String> attributeValues = SQLUtils.getCleanedSearchableValues(attributeInValue, attributeDataType);
262 
263         if (attributeValues == null || attributeValues.isEmpty()) {
264             return;
265         }
266 
267         for (String attributeValue : attributeValues) {
268 
269             // FIXME: JLR : Replacing this logic with KS-style validation is trickier, since KS validation requires a DataProvider object that can
270             // look back and find other attribute values aside from the one we're working on.
271             // Also - the date stuff below is implemented very differently.
272             //validator.validateAttributeField(businessObject, fieldName);
273 
274             if (StringUtils.isNotBlank(attributeValue)) {
275                 Integer minLength = getDataDictionaryService().getAttributeMinLength(objectClassName, attributeName);
276                 if ((minLength != null) && (minLength.intValue() > attributeValue.length())) {
277                     String errorLabel = getDataDictionaryService().getAttributeErrorLabel(objectClassName,
278                             attributeName);
279                     GlobalVariables.getMessageMap().putError(errorKey, RiceKeyConstants.ERROR_MIN_LENGTH,
280                             new String[]{errorLabel, minLength.toString()});
281                     return;
282                 }
283                 Integer maxLength = getDataDictionaryService().getAttributeMaxLength(objectClassName, attributeName);
284                 if ((maxLength != null) && (maxLength.intValue() < attributeValue.length())) {
285                     String errorLabel = getDataDictionaryService().getAttributeErrorLabel(objectClassName,
286                             attributeName);
287                     GlobalVariables.getMessageMap().putError(errorKey, RiceKeyConstants.ERROR_MAX_LENGTH,
288                             new String[]{errorLabel, maxLength.toString()});
289                     return;
290                 }
291                 Pattern validationExpression = getDataDictionaryService().getAttributeValidatingExpression(
292                         objectClassName, attributeName);
293                 if (validationExpression != null && !validationExpression.pattern().equals(".*")) {
294                     if (LOG.isDebugEnabled()) {
295                         LOG.debug("(bo, attributeName, validationExpression) = (" + objectClassName + "," +
296                                 attributeName + "," + validationExpression + ")");
297                     }
298 
299                     if (!validationExpression.matcher(attributeValue).matches()) {
300                         // Retrieving formatter class
301                         if (formatterClass == null) {
302                             // this is just a cache check... all dates ranges get called twice
303                             formatterClass = getDataDictionaryService().getAttributeFormatter(objectClassName,
304                                     attributeName);
305                         }
306 
307                         if (formatterClass != null) {
308                             boolean valuesAreValid = true;
309                             boolean isError = true;
310                             String errorKeyPrefix = "";
311                             try {
312 
313                                 // this is a special case for date ranges in order to set the proper error message
314                                 if (DateFormatter.class.isAssignableFrom(formatterClass)) {
315                                     String[] values = attributeInValue.split("\\.\\."); // is it a range
316                                     if (values.length == 2 &&
317                                             attributeValues.size() == 2) { // make sure it's not like a .. b | c
318                                         checkDateBounds = true; // now we need to check that a <= b
319                                         if (attributeValues.indexOf(attributeValue) ==
320                                                 0) { // only care about lower bound
321                                             errorKeyPrefix = KRADConstants.LOOKUP_RANGE_LOWER_BOUND_PROPERTY_PREFIX;
322                                         }
323                                     }
324                                 }
325 
326                                 Method validatorMethod = formatterClass.getDeclaredMethod(VALIDATE_METHOD,
327                                         new Class<?>[]{String.class});
328                                 Object o = validatorMethod.invoke(formatterClass.newInstance(), attributeValue);
329                                 if (o instanceof Boolean) {
330                                     isError = !((Boolean) o).booleanValue();
331                                 }
332                                 valuesAreValid &= !isError;
333                             } catch (Exception e) {
334                                 if (LOG.isDebugEnabled()) {
335                                     LOG.debug(e.getMessage(), e);
336                                 }
337                                 isError = true;
338                                 valuesAreValid = false;
339                             }
340                             if (isError) {
341                                 checkDateBounds = false; // it's already invalid, no need to check date bounds
342                                 String errorMessageKey =
343                                         getDataDictionaryService().getAttributeValidatingErrorMessageKey(
344                                                 objectClassName, attributeName);
345                                 String[] errorMessageParameters =
346                                         getDataDictionaryService().getAttributeValidatingErrorMessageParameters(
347                                                 objectClassName, attributeName);
348                                 GlobalVariables.getMessageMap().putError(errorKeyPrefix + errorKey, errorMessageKey,
349                                         errorMessageParameters);
350                             }
351                         } else {
352                             // if it fails the default validation and has no formatter class then it's still a std failure.
353                             String errorMessageKey = getDataDictionaryService().getAttributeValidatingErrorMessageKey(
354                                     objectClassName, attributeName);
355                             String[] errorMessageParameters =
356                                     getDataDictionaryService().getAttributeValidatingErrorMessageParameters(
357                                             objectClassName, attributeName);
358                             GlobalVariables.getMessageMap().putError(errorKey, errorMessageKey, errorMessageParameters);
359                         }
360                     }
361                 }
362                 /*BigDecimal*/
363                 String exclusiveMin = getDataDictionaryService().getAttributeExclusiveMin(objectClassName,
364                         attributeName);
365                 if (exclusiveMin != null) {
366                     try {
367                         BigDecimal exclusiveMinBigDecimal = new BigDecimal(exclusiveMin);
368                         if (exclusiveMinBigDecimal.compareTo(new BigDecimal(attributeValue)) >= 0) {
369                             String errorLabel = getDataDictionaryService().getAttributeErrorLabel(objectClassName,
370                                     attributeName);
371                             GlobalVariables.getMessageMap().putError(errorKey, RiceKeyConstants.ERROR_EXCLUSIVE_MIN,
372                                     // todo: Formatter for currency?
373                                     new String[]{errorLabel, exclusiveMin.toString()});
374                             return;
375                         }
376                     } catch (NumberFormatException e) {
377                         // quash; this indicates that the DD contained a min for a non-numeric attribute
378                     }
379                 }
380                 /*BigDecimal*/
381                 String inclusiveMax = getDataDictionaryService().getAttributeInclusiveMax(objectClassName,
382                         attributeName);
383                 if (inclusiveMax != null) {
384                     try {
385                         BigDecimal inclusiveMaxBigDecimal = new BigDecimal(inclusiveMax);
386                         if (inclusiveMaxBigDecimal.compareTo(new BigDecimal(attributeValue)) < 0) {
387                             String errorLabel = getDataDictionaryService().getAttributeErrorLabel(objectClassName,
388                                     attributeName);
389                             GlobalVariables.getMessageMap().putError(errorKey, RiceKeyConstants.ERROR_INCLUSIVE_MAX,
390                                     // todo: Formatter for currency?
391                                     new String[]{errorLabel, inclusiveMax.toString()});
392                             return;
393                         }
394                     } catch (NumberFormatException e) {
395                         // quash; this indicates that the DD contained a max for a non-numeric attribute
396                     }
397                 }
398             }
399         }
400 
401         if (checkDateBounds) {
402             // this means that we only have 2 values and it's a date range.
403             java.sql.Timestamp lVal = null;
404             java.sql.Timestamp uVal = null;
405             try {
406                 lVal = CoreApiServiceLocator.getDateTimeService().convertToSqlTimestamp(attributeValues.get(0));
407                 uVal = CoreApiServiceLocator.getDateTimeService().convertToSqlTimestamp(attributeValues.get(1));
408             } catch (Exception ex) {
409                 // this shouldn't happen because the tests passed above.
410                 String errorMessageKey = getDataDictionaryService().getAttributeValidatingErrorMessageKey(
411                         objectClassName, attributeName);
412                 String[] errorMessageParameters =
413                         getDataDictionaryService().getAttributeValidatingErrorMessageParameters(objectClassName,
414                                 attributeName);
415                 GlobalVariables.getMessageMap().putError(
416                         KRADConstants.LOOKUP_RANGE_LOWER_BOUND_PROPERTY_PREFIX + errorKey, errorMessageKey,
417                         errorMessageParameters);
418             }
419 
420             if (lVal != null && lVal.compareTo(uVal) > 0) { // check the bounds
421                 String errorMessageKey = getDataDictionaryService().getAttributeValidatingErrorMessageKey(
422                         objectClassName, attributeName);
423                 String[] errorMessageParameters =
424                         getDataDictionaryService().getAttributeValidatingErrorMessageParameters(objectClassName,
425                                 attributeName);
426                 GlobalVariables.getMessageMap().putError(
427                         KRADConstants.LOOKUP_RANGE_LOWER_BOUND_PROPERTY_PREFIX + errorKey, errorMessageKey + ".range",
428                         errorMessageParameters);
429             }
430         }
431     }
432 
433     // FIXME: JLR - this is now redundant and should be using the same code as the required processing elsewhere, but the control definition stuff doesn't really fit
434     // it doesn't seem to be used anywhere
435     @Override
436     @Deprecated
437     public void validateAttributeRequired(String objectClassName, String attributeName, Object attributeValue,
438             Boolean forMaintenance, String errorKey) {
439         // check if field is a required field for the business object
440         if (attributeValue == null || (attributeValue instanceof String && StringUtils.isBlank(
441                 (String) attributeValue))) {
442             Boolean required = getDataDictionaryService().isAttributeRequired(objectClassName, attributeName);
443             ControlDefinition controlDef = getDataDictionaryService().getAttributeControlDefinition(objectClassName,
444                     attributeName);
445 
446             if (required != null && required.booleanValue() && !(controlDef != null && controlDef.isHidden())) {
447 
448                 // get label of attribute for message
449                 String errorLabel = getDataDictionaryService().getAttributeErrorLabel(objectClassName, attributeName);
450                 GlobalVariables.getMessageMap().putError(errorKey, RiceKeyConstants.ERROR_REQUIRED, errorLabel);
451             }
452         }
453     }
454 }