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