View Javadoc

1   /**
2    * Copyright 2005-2011 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.framework.persistence.jdbc.sql.SQLUtils;
23  import org.kuali.rice.core.web.format.DateFormatter;
24  import org.kuali.rice.kns.datadictionary.MaintenanceDocumentEntry;
25  import org.kuali.rice.kns.datadictionary.validation.MaintenanceDocumentAttributeValueReader;
26  import org.kuali.rice.kns.service.DictionaryValidationService;
27  import org.kuali.rice.krad.bo.BusinessObject;
28  import org.kuali.rice.krad.datadictionary.control.ControlDefinition;
29  import org.kuali.rice.krad.document.Document;
30  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
31  import org.kuali.rice.krad.util.GlobalVariables;
32  import org.kuali.rice.krad.util.KRADConstants;
33  
34  import java.lang.reflect.Method;
35  import java.math.BigDecimal;
36  import java.util.List;
37  import java.util.regex.Pattern;
38  
39  /**
40   * @author Kuali Rice Team (rice.collab@kuali.org)
41   */
42  @Deprecated
43  public class DictionaryValidationServiceImpl extends org.kuali.rice.krad.service.impl.DictionaryValidationServiceImpl implements DictionaryValidationService {
44      private static org.apache.log4j.Logger LOG =
45              org.apache.log4j.Logger.getLogger(DictionaryValidationServiceImpl.class);
46  
47      /**
48       * @see org.kuali.rice.krad.service.DictionaryValidationService#validateDocumentRecursively
49       */
50      @Deprecated
51      public void validateDocumentRecursively(Document document, int depth) {
52          // validate primitives of document
53          validateDocument(document);
54  
55          // call method to recursively find business objects and validate
56          validateBusinessObjectsFromDescriptors(document, PropertyUtils.getPropertyDescriptors(document.getClass()),
57                  depth);
58      }
59  
60      /**
61       * @see org.kuali.rice.krad.service.DictionaryValidationService#validateBusinessObjectOnMaintenanceDocument(org.kuali.rice.krad.bo.BusinessObject,
62       *      java.lang.String)
63       * @deprecated since 1.1
64       */
65      @Deprecated
66      public void validateBusinessObjectOnMaintenanceDocument(BusinessObject businessObject, String docTypeName) {
67  
68          MaintenanceDocumentEntry entry = (MaintenanceDocumentEntry)
69                  KRADServiceLocatorWeb.getDocumentDictionaryService().getMaintenanceDocumentEntry(docTypeName);
70          validate(new MaintenanceDocumentAttributeValueReader(businessObject, docTypeName, entry,
71                  persistenceStructureService), true);
72      }
73  
74  //	protected void validateBusinessObjectOnMaintenanceDocumentHelper(BusinessObject businessObject, List<? extends MaintainableItemDefinition> itemDefinitions, String errorPrefix) {
75  //
76  //		for (MaintainableItemDefinition itemDefinition : itemDefinitions) {
77  //			if (itemDefinition instanceof MaintainableFieldDefinition) {
78  //		        if (getDataDictionaryService().isAttributeDefined(businessObject.getClass(), itemDefinition.getName())) {
79  //		            Object value = ObjectUtils.getPropertyValue(businessObject, itemDefinition.getName());
80  //		            if (value != null && StringUtils.isNotBlank(value.toString())) {
81  //			            Class propertyType = ObjectUtils.getPropertyType(businessObject, itemDefinition.getName(), persistenceStructureService);
82  //			            if (TypeUtils.isStringClass(propertyType) || TypeUtils.isIntegralClass(propertyType) || TypeUtils.isDecimalClass(propertyType) || TypeUtils.isTemporalClass(propertyType)) {
83  //			                // check value format against dictionary
84  //		                    if (!TypeUtils.isTemporalClass(propertyType)) {
85  //		                        validateAttributeFormat(businessObject.getClass().getName(), itemDefinition.getName(), value.toString(), errorPrefix + itemDefinition.getName());
86  //		                    }
87  //			            }
88  //		            }
89  //		        }
90  //			}
91  //			/*
92  //			TODO: reenable when we come up with a strategy to handle fields that are not editable
93  //			else if (itemDefinition instanceof MaintainableCollectionDefinition) {
94  //				MaintainableCollectionDefinition collectionDefinition = (MaintainableCollectionDefinition) itemDefinition;
95  //				Collection<BusinessObject> c = (Collection<BusinessObject>) ObjectUtils.getPropertyValue(businessObject, itemDefinition.getName());
96  //				if (c != null) {
97  //					int i = 0;
98  //					for (BusinessObject o : c) {
99  //						String newErrorPrefix = errorPrefix + itemDefinition.getName() + "[" + i + "].";
100 //						validateBusinessObjectOnMaintenanceDocumentHelper(o, collectionDefinition.getMaintainableCollections(), newErrorPrefix);
101 //						validateBusinessObjectOnMaintenanceDocumentHelper(o, collectionDefinition.getMaintainableFields(), newErrorPrefix);
102 //						i++;
103 //					}
104 //				}
105 //			}*/
106 //		}
107 //	}
108 
109     /**
110      * @see org.kuali.rice.krad.service.DictionaryValidationService#validateAttributeFormat
111      *      objectClassName is the docTypeName
112      * @deprecated since 1.1
113      */
114     @Deprecated
115     public void validateAttributeFormat(String objectClassName, String attributeName, String attributeInValue,
116             String errorKey) {
117         // 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.
118         String attributeDataType = null;
119         try {
120             attributeDataType = getWorkflowAttributePropertyResolutionService().determineFieldDataType(
121                     (Class<? extends BusinessObject>) Class.forName(
122                             getDataDictionaryService().getDataDictionary().getDictionaryObjectEntry(objectClassName)
123                                     .getFullClassName()), attributeName);
124         } catch (ClassNotFoundException e) {
125             attributeDataType = KRADConstants.DATA_TYPE_STRING;
126         } catch (NullPointerException e) {
127             attributeDataType = KRADConstants.DATA_TYPE_STRING;
128         }
129 
130         validateAttributeFormat(objectClassName, attributeName, attributeInValue, attributeDataType, errorKey);
131     }
132 
133     /**
134      * The attributeDataType parameter should be one of the data types specified by the SearchableAttribute
135      * interface;
136      * will
137      * default to DATA_TYPE_STRING if a data type other than the ones from SearchableAttribute is specified.
138      *
139      * @see org.kuali.rice.krad.service.DictionaryValidationService#validateAttributeFormat(java.lang.String,
140      *      java.lang.String, java.lang.String, java.lang.String, java.lang.String)
141      *      objectClassName is the docTypeName
142      * @deprecated since 1.1
143      */
144     @Deprecated
145     public void validateAttributeFormat(String objectClassName, String attributeName, String attributeInValue,
146             String attributeDataType, String errorKey) {
147         boolean checkDateBounds = false; // this is used so we can check date bounds
148         Class<?> formatterClass = null;
149 
150         if (LOG.isDebugEnabled()) {
151             LOG.debug("(bo, attributeName, attributeValue) = (" + objectClassName + "," + attributeName + "," +
152                     attributeInValue + ")");
153         }
154 
155         /*
156         *  This will return a list of searchable attributes. so if the value is
157         *  12/07/09 .. 12/08/09 it will return [12/07/09,12/08/09]
158         */
159 
160         final List<String> attributeValues = SQLUtils.getCleanedSearchableValues(attributeInValue, attributeDataType);
161 
162         if (attributeValues == null || attributeValues.isEmpty()) {
163             return;
164         }
165 
166         for (String attributeValue : attributeValues) {
167 
168             // FIXME: JLR : Replacing this logic with KS-style validation is trickier, since KS validation requires a DataProvider object that can
169             // look back and find other attribute values aside from the one we're working on.
170             // Also - the date stuff below is implemented very differently.
171             //validator.validateAttributeField(businessObject, fieldName);
172 
173             if (StringUtils.isNotBlank(attributeValue)) {
174                 Integer minLength = getDataDictionaryService().getAttributeMinLength(objectClassName, attributeName);
175                 if ((minLength != null) && (minLength.intValue() > attributeValue.length())) {
176                     String errorLabel =
177                             getDataDictionaryService().getAttributeErrorLabel(objectClassName, attributeName);
178                     GlobalVariables.getMessageMap().putError(errorKey, RiceKeyConstants.ERROR_MIN_LENGTH,
179                             new String[]{errorLabel, minLength.toString()});
180                     return;
181                 }
182                 Integer maxLength = getDataDictionaryService().getAttributeMaxLength(objectClassName, attributeName);
183                 if ((maxLength != null) && (maxLength.intValue() < attributeValue.length())) {
184                     String errorLabel =
185                             getDataDictionaryService().getAttributeErrorLabel(objectClassName, attributeName);
186                     GlobalVariables.getMessageMap().putError(errorKey, RiceKeyConstants.ERROR_MAX_LENGTH,
187                             new String[]{errorLabel, maxLength.toString()});
188                     return;
189                 }
190                 Pattern validationExpression =
191                         getDataDictionaryService().getAttributeValidatingExpression(objectClassName, attributeName);
192                 if (validationExpression != null && !validationExpression.pattern().equals(".*")) {
193                     if (LOG.isDebugEnabled()) {
194                         LOG.debug("(bo, attributeName, validationExpression) = (" + objectClassName + "," +
195                                 attributeName + "," + validationExpression + ")");
196                     }
197 
198                     if (!validationExpression.matcher(attributeValue).matches()) {
199                         // Retrieving formatter class
200                         if (formatterClass == null) {
201                             // this is just a cache check... all dates ranges get called twice
202                             formatterClass =
203                                     getDataDictionaryService().getAttributeFormatter(objectClassName, attributeName);
204                         }
205 
206                         if (formatterClass != null) {
207                             boolean valuesAreValid = true;
208                             boolean isError = true;
209                             String errorKeyPrefix = "";
210                             try {
211 
212                                 // this is a special case for date ranges in order to set the proper error message
213                                 if (DateFormatter.class.isAssignableFrom(formatterClass)) {
214                                     String[] values = attributeInValue.split("\\.\\."); // is it a range
215                                     if (values.length == 2 &&
216                                             attributeValues.size() == 2) { // make sure it's not like a .. b | c
217                                         checkDateBounds = true; // now we need to check that a <= b
218                                         if (attributeValues.indexOf(attributeValue) ==
219                                                 0) { // only care about lower bound
220                                             errorKeyPrefix = KRADConstants.LOOKUP_RANGE_LOWER_BOUND_PROPERTY_PREFIX;
221                                         }
222                                     }
223                                 }
224 
225                                 Method validatorMethod =
226                                         formatterClass.getDeclaredMethod(VALIDATE_METHOD, new Class<?>[]{String.class});
227                                 Object o = validatorMethod.invoke(formatterClass.newInstance(), attributeValue);
228                                 if (o instanceof Boolean) {
229                                     isError = !((Boolean) o).booleanValue();
230                                 }
231                                 valuesAreValid &= !isError;
232                             } catch (Exception e) {
233                                 if (LOG.isDebugEnabled()) {
234                                     LOG.debug(e.getMessage(), e);
235                                 }
236                                 isError = true;
237                                 valuesAreValid = false;
238                             }
239                             if (isError) {
240                                 checkDateBounds = false; // it's already invalid, no need to check date bounds
241                                 String errorMessageKey = getDataDictionaryService()
242                                         .getAttributeValidatingErrorMessageKey(objectClassName, attributeName);
243                                 String[] errorMessageParameters = getDataDictionaryService()
244                                         .getAttributeValidatingErrorMessageParameters(objectClassName, attributeName);
245                                 GlobalVariables.getMessageMap()
246                                         .putError(errorKeyPrefix + errorKey, errorMessageKey, errorMessageParameters);
247                             }
248                         } else {
249                             // if it fails the default validation and has no formatter class then it's still a std failure.
250                             String errorMessageKey = getDataDictionaryService()
251                                     .getAttributeValidatingErrorMessageKey(objectClassName, attributeName);
252                             String[] errorMessageParameters = getDataDictionaryService()
253                                     .getAttributeValidatingErrorMessageParameters(objectClassName, attributeName);
254                             GlobalVariables.getMessageMap().putError(errorKey, errorMessageKey, errorMessageParameters);
255                         }
256                     }
257                 }
258                 /*BigDecimal*/
259                 String exclusiveMin =
260                         getDataDictionaryService().getAttributeExclusiveMin(objectClassName, attributeName);
261                 if (exclusiveMin != null) {
262                     try {
263                         BigDecimal exclusiveMinBigDecimal = new BigDecimal(exclusiveMin);
264                         if (exclusiveMinBigDecimal.compareTo(new BigDecimal(attributeValue)) >= 0) {
265                             String errorLabel =
266                                     getDataDictionaryService().getAttributeErrorLabel(objectClassName, attributeName);
267                             GlobalVariables.getMessageMap().putError(errorKey, RiceKeyConstants.ERROR_EXCLUSIVE_MIN,
268                                     // todo: Formatter for currency?
269                                     new String[]{errorLabel, exclusiveMin.toString()});
270                             return;
271                         }
272                     } catch (NumberFormatException e) {
273                         // quash; this indicates that the DD contained a min for a non-numeric attribute
274                     }
275                 }
276                 /*BigDecimal*/
277                 String inclusiveMax =
278                         getDataDictionaryService().getAttributeInclusiveMax(objectClassName, attributeName);
279                 if (inclusiveMax != null) {
280                     try {
281                         BigDecimal inclusiveMaxBigDecimal = new BigDecimal(inclusiveMax);
282                         if (inclusiveMaxBigDecimal.compareTo(new BigDecimal(attributeValue)) < 0) {
283                             String errorLabel =
284                                     getDataDictionaryService().getAttributeErrorLabel(objectClassName, attributeName);
285                             GlobalVariables.getMessageMap().putError(errorKey, RiceKeyConstants.ERROR_INCLUSIVE_MAX,
286                                     // todo: Formatter for currency?
287                                     new String[]{errorLabel, inclusiveMax.toString()});
288                             return;
289                         }
290                     } catch (NumberFormatException e) {
291                         // quash; this indicates that the DD contained a max for a non-numeric attribute
292                     }
293                 }
294             }
295         }
296 
297         if (checkDateBounds) {
298             // this means that we only have 2 values and it's a date range.
299             java.sql.Timestamp lVal = null;
300             java.sql.Timestamp uVal = null;
301             try {
302                 lVal = CoreApiServiceLocator.getDateTimeService().convertToSqlTimestamp(attributeValues.get(0));
303                 uVal = CoreApiServiceLocator.getDateTimeService().convertToSqlTimestamp(attributeValues.get(1));
304             } catch (Exception ex) {
305                 // this shouldn't happen because the tests passed above.
306                 String errorMessageKey = getDataDictionaryService()
307                         .getAttributeValidatingErrorMessageKey(objectClassName, attributeName);
308                 String[] errorMessageParameters = getDataDictionaryService()
309                         .getAttributeValidatingErrorMessageParameters(objectClassName, attributeName);
310                 GlobalVariables.getMessageMap()
311                         .putError(KRADConstants.LOOKUP_RANGE_LOWER_BOUND_PROPERTY_PREFIX + errorKey, errorMessageKey,
312                                 errorMessageParameters);
313             }
314 
315             if (lVal != null && lVal.compareTo(uVal) > 0) { // check the bounds
316                 String errorMessageKey = getDataDictionaryService()
317                         .getAttributeValidatingErrorMessageKey(objectClassName, attributeName);
318                 String[] errorMessageParameters = getDataDictionaryService()
319                         .getAttributeValidatingErrorMessageParameters(objectClassName, attributeName);
320                 GlobalVariables.getMessageMap()
321                         .putError(KRADConstants.LOOKUP_RANGE_LOWER_BOUND_PROPERTY_PREFIX + errorKey,
322                                 errorMessageKey + ".range", errorMessageParameters);
323             }
324         }
325     }
326 
327     /**
328      * @see org.kuali.rice.krad.service.DictionaryValidationService#validateAttributeRequired
329      */
330     // 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
331     // it doesn't seem to be used anywhere
332     @Deprecated
333     public void validateAttributeRequired(String objectClassName, String attributeName, Object attributeValue,
334             Boolean forMaintenance, String errorKey) {
335         // check if field is a required field for the business object
336         if (attributeValue == null ||
337                 (attributeValue instanceof String && StringUtils.isBlank((String) attributeValue))) {
338             Boolean required = getDataDictionaryService().isAttributeRequired(objectClassName, attributeName);
339             ControlDefinition controlDef =
340                     getDataDictionaryService().getAttributeControlDefinition(objectClassName, attributeName);
341 
342             if (required != null && required.booleanValue() && !(controlDef != null && controlDef.isHidden())) {
343 
344                 // get label of attribute for message
345                 String errorLabel = getDataDictionaryService().getAttributeErrorLabel(objectClassName, attributeName);
346                 GlobalVariables.getMessageMap().putError(errorKey, RiceKeyConstants.ERROR_REQUIRED, errorLabel);
347             }
348         }
349     }
350 }