001 /**
002 * Copyright 2005-2012 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016 package org.kuali.rice.kns.service.impl;
017
018 import org.apache.commons.beanutils.PropertyUtils;
019 import org.apache.commons.lang.StringUtils;
020 import org.kuali.rice.core.api.CoreApiServiceLocator;
021 import org.kuali.rice.core.api.util.RiceKeyConstants;
022 import org.kuali.rice.core.api.util.type.TypeUtils;
023 import org.kuali.rice.core.framework.persistence.jdbc.sql.SQLUtils;
024 import org.kuali.rice.core.web.format.DateFormatter;
025 import org.kuali.rice.kns.datadictionary.MaintainableFieldDefinition;
026 import org.kuali.rice.kns.datadictionary.MaintainableItemDefinition;
027 import org.kuali.rice.kns.datadictionary.MaintainableSectionDefinition;
028 import org.kuali.rice.kns.datadictionary.MaintenanceDocumentEntry;
029 import org.kuali.rice.kns.service.DictionaryValidationService;
030 import org.kuali.rice.kns.service.KNSServiceLocator;
031 import org.kuali.rice.krad.bo.BusinessObject;
032 import org.kuali.rice.krad.datadictionary.control.ControlDefinition;
033 import org.kuali.rice.krad.document.Document;
034 import org.kuali.rice.krad.util.GlobalVariables;
035 import org.kuali.rice.krad.util.KRADConstants;
036 import org.kuali.rice.krad.util.ObjectUtils;
037
038 import java.beans.PropertyDescriptor;
039 import java.lang.reflect.Method;
040 import java.math.BigDecimal;
041 import java.util.List;
042 import java.util.regex.Pattern;
043
044 /**
045 * @author Kuali Rice Team (rice.collab@kuali.org)
046 */
047 @Deprecated
048 public class DictionaryValidationServiceImpl extends org.kuali.rice.krad.service.impl.DictionaryValidationServiceImpl implements DictionaryValidationService {
049 private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(
050 DictionaryValidationServiceImpl.class);
051
052 /**
053 * @see org.kuali.rice.krad.service.DictionaryValidationService#validateDocumentAndUpdatableReferencesRecursively(org.kuali.rice.krad.document.Document, int, boolean, boolean)
054 * @deprecated since 2.1
055 */
056 @Override
057 @Deprecated
058 public void validateDocumentAndUpdatableReferencesRecursively(Document document, int maxDepth,
059 boolean validateRequired, boolean chompLastLetterSFromCollectionName) {
060 // Use the KNS validation code here -- this overrides the behavior in the krad version which calls validate(...)
061 validateBusinessObject(document, validateRequired);
062
063 if (maxDepth > 0) {
064 validateUpdatabableReferencesRecursively(document, maxDepth - 1, validateRequired,
065 chompLastLetterSFromCollectionName, newIdentitySet());
066 }
067 }
068
069 /**
070 * @see org.kuali.rice.kns.service.DictionaryValidationService#validateDocumentRecursively(org.kuali.rice.krad.document.Document, int)
071 * @deprecated since 2.0
072 */
073 @Deprecated
074 @Override
075 public void validateDocumentRecursively(Document document, int depth) {
076 // validate primitives of document
077 validateDocument(document);
078
079 // call method to recursively find business objects and validate
080 validateBusinessObjectsFromDescriptors(document, PropertyUtils.getPropertyDescriptors(document.getClass()),
081 depth);
082 }
083
084 /**
085 * @see org.kuali.rice.kns.service.DictionaryValidationService#validateDocument(org.kuali.rice.krad.document.Document)
086 * @param document - document to validate
087 * @deprecated since 2.1.2
088 */
089 @Deprecated
090 @Override
091 public void validateDocument(Document document) {
092 String documentEntryName = document.getDocumentHeader().getWorkflowDocument().getDocumentTypeName();
093
094 validatePrimitivesFromDescriptors(documentEntryName, document, PropertyUtils.getPropertyDescriptors(document.getClass()), "", true);
095 }
096
097 @Override
098 @Deprecated
099 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 }