View Javadoc

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