001: /*
002: * Copyright 2001-2007 Geert Bevin <gbevin[remove] at uwyn dot com>
003: * Distributed under the terms of either:
004: * - the common development and distribution license (CDDL), v1.0; or
005: * - the GNU Lesser General Public License, v2.1 or later
006: * $Id: Validation.java 3783 2007-06-11 11:32:57Z gbevin $
007: */
008: package com.uwyn.rife.site;
009:
010: import java.util.*;
011:
012: import com.uwyn.rife.tools.ExceptionUtils;
013: import com.uwyn.rife.tools.ObjectUtils;
014: import java.util.logging.Logger;
015:
016: public class Validation<B extends ConstrainedBean, P extends ConstrainedProperty>
017: implements ValidatedConstrained<P>, Cloneable,
018: Constrained<B, P>, ConstrainedPropertyListener {
019: private boolean mActivated = false;
020:
021: private Validated mValidatedBean = null;
022: private List<ValidationRule> mValidationRules = null;
023: private List<String> mValidatedSubjects = null;
024: private Map<String, P> mConstrainedProperties = null;
025: private Set<String> mActivePropertyConstraints = null;
026: private Map<String, ValidationGroup<P>> mValidationGroups = null;
027: private B mConstrainedBean = null;
028:
029: private Set<ValidationError> mValidationErrors = null;
030: private List<String> mErrorLimitedSubjects = null;
031:
032: public Validation() {
033: }
034:
035: /**
036: * This method is called at least once and maximum once when any method
037: * related to Validated rules, subjects and group or Constrained
038: * properties are used.
039: * <p>By overriding this method, you can thus isolate all the validation
040: * setup code code and don't enforce a performance penalty at each object
041: * construction when doing it in the default constructor.
042: *
043: * @since 1.0
044: */
045: protected void activateValidation() {
046: }
047:
048: public void provideValidatedBean(Validated bean) {
049: if (mValidationRules != null) {
050: for (ValidationRule rule : mValidationRules) {
051: if (rule.getBean() == mValidatedBean) {
052: rule.setBean(bean);
053: }
054: }
055: }
056:
057: mValidatedBean = bean;
058: }
059:
060: public Validated retrieveValidatedBean() {
061: if (null == mValidatedBean) {
062: return this ;
063: }
064:
065: return mValidatedBean;
066: }
067:
068: private void ensureActivatedValidation() {
069: if (mActivated) {
070: return;
071: }
072: mActivated = true;
073:
074: activateValidation();
075: }
076:
077: public ValidationGroup<P> addGroup(String name) {
078: ensureActivatedValidation();
079:
080: if (null == mValidationGroups) {
081: mValidationGroups = new HashMap<String, ValidationGroup<P>>();
082: }
083:
084: ValidationGroup<P> group = new ValidationGroup<P>(name, this );
085: mValidationGroups.put(name, group);
086: return group;
087: }
088:
089: public void focusGroup(String name) {
090: ensureActivatedValidation();
091:
092: if (null == mValidationGroups || null == mValidationErrors
093: || null == name) {
094: return;
095: }
096:
097: ValidationGroup<P> group = mValidationGroups.get(name);
098: if (null == group) {
099: return;
100: }
101:
102: Set<ValidationError> retained_errors = new LinkedHashSet<ValidationError>();
103: List<String> retained_subjects = group.getSubjects();
104: for (ValidationError error : mValidationErrors) {
105: if (retained_subjects.contains(error.getSubject())) {
106: retained_errors.add(error);
107: }
108: }
109: mValidationErrors = retained_errors;
110: }
111:
112: public void resetGroup(String name) {
113: ensureActivatedValidation();
114:
115: if (null == mValidationGroups || null == mValidationErrors
116: || null == name) {
117: return;
118: }
119:
120: ValidationGroup<P> group = mValidationGroups.get(name);
121: if (null == group) {
122: return;
123: }
124:
125: Set<ValidationError> retained_errors = new LinkedHashSet<ValidationError>();
126: List<String> group_subjects = group.getSubjects();
127: for (ValidationError error : mValidationErrors) {
128: if (!group_subjects.contains(error.getSubject())) {
129: retained_errors.add(error);
130: }
131: }
132: mValidationErrors = retained_errors;
133: }
134:
135: public void addRule(ValidationRule rule) {
136: ensureActivatedValidation();
137:
138: if (null == rule) {
139: return;
140: }
141:
142: if (null == mValidationRules) {
143: mValidationRules = new ArrayList<ValidationRule>();
144: }
145: if (null == mValidatedSubjects) {
146: mValidatedSubjects = new ArrayList<String>();
147: }
148:
149: if (null == rule.getBean()) {
150: rule.setBean(retrieveValidatedBean());
151: }
152:
153: mValidationRules.add(rule);
154: String subject = rule.getSubject();
155: if (!mValidatedSubjects.contains(subject)) {
156: mValidatedSubjects.add(subject);
157: }
158: }
159:
160: private ValidationRule addConstrainedPropertyRule(
161: P constrainedProperty, PropertyValidationRule rule) {
162: rule.setConstrainedProperty(constrainedProperty);
163: rule.setSubject(constrainedProperty.getSubjectName());
164: addRule(rule);
165:
166: return rule;
167: }
168:
169: public List<PropertyValidationRule> generateConstrainedPropertyRules(
170: P constrainedProperty) {
171: List<PropertyValidationRule> rules = new ArrayList<PropertyValidationRule>();
172: if (constrainedProperty.isNotNull()) {
173: rules.add(new ValidationRuleNotNull(constrainedProperty
174: .getPropertyName()));
175: }
176: if (constrainedProperty.isNotEmpty()) {
177: rules.add(new ValidationRuleNotEmpty(constrainedProperty
178: .getPropertyName()));
179: }
180: if (constrainedProperty.isNotEqual()) {
181: rules.add(new ValidationRuleNotEqual(constrainedProperty
182: .getPropertyName(), constrainedProperty
183: .getNotEqual()));
184: }
185: if (constrainedProperty.hasLimitedLength()) {
186: rules.add(new ValidationRuleLimitedLength(
187: constrainedProperty.getPropertyName(),
188: constrainedProperty.getMinLength(),
189: constrainedProperty.getMaxLength()));
190: }
191: if (constrainedProperty.isEmail()) {
192: rules.add(new ValidationRuleEmail(constrainedProperty
193: .getPropertyName()));
194: }
195: if (constrainedProperty.isUrl()) {
196: rules.add(new ValidationRuleUrl(constrainedProperty
197: .getPropertyName()));
198: }
199: if (constrainedProperty.matchesRegexp()) {
200: rules
201: .add(new ValidationRuleRegexp(constrainedProperty
202: .getPropertyName(), constrainedProperty
203: .getRegexp()));
204: }
205: if (constrainedProperty.isLimitedDate()) {
206: rules.add(new ValidationRuleLimitedDate(constrainedProperty
207: .getPropertyName(), constrainedProperty
208: .getMinDate(), constrainedProperty.getMaxDate()));
209: }
210: if (constrainedProperty.isInList()) {
211: rules
212: .add(new ValidationRuleInList(constrainedProperty
213: .getPropertyName(), constrainedProperty
214: .getInList()));
215: }
216: if (constrainedProperty.isRange()) {
217: rules
218: .add(new ValidationRuleRange(constrainedProperty
219: .getPropertyName(), constrainedProperty
220: .getRangeBegin(), constrainedProperty
221: .getRangeEnd()));
222: }
223: if (constrainedProperty.isSameAs()) {
224: rules
225: .add(new ValidationRuleSameAs(constrainedProperty
226: .getPropertyName(), constrainedProperty
227: .getSameAs()));
228: }
229: if (constrainedProperty.isFormatted()) {
230: rules
231: .add(new ValidationRuleFormat(constrainedProperty
232: .getPropertyName(), constrainedProperty
233: .getFormat()));
234: }
235: if (constrainedProperty.hasMimeType()) {
236: PropertyValidationRule rule = constrainedProperty
237: .getMimeType().getValidationRule(
238: constrainedProperty);
239: if (rule != null) {
240: rules.add(rule);
241: }
242: }
243:
244: return rules;
245: }
246:
247: public List<PropertyValidationRule> addConstrainedPropertyRules(
248: P constrainedProperty) {
249: ensureActivatedValidation();
250:
251: if (null == constrainedProperty) {
252: return null;
253: }
254:
255: if (null == mConstrainedProperties) {
256: mConstrainedProperties = new LinkedHashMap<String, P>();
257: }
258:
259: // store the constrained property and obtain the old one if it exists
260: P old_constrained_property = mConstrainedProperties.put(
261: constrainedProperty.getPropertyName(),
262: constrainedProperty);
263: if (old_constrained_property != null
264: && mValidationRules != null) {
265: // obtain all validation rules that were generated by the old constrained property
266: ArrayList<ValidationRule> rules_to_remove = new ArrayList<ValidationRule>();
267: for (ValidationRule rule : mValidationRules) {
268: if (rule instanceof PropertyValidationRule) {
269: if (old_constrained_property == ((PropertyValidationRule) rule)
270: .getConstrainedProperty()) {
271: rules_to_remove.add(rule);
272: }
273: }
274: }
275:
276: // remove all validation rules that were generated by the old constrained property
277: mValidationRules.removeAll(rules_to_remove);
278:
279: // merge constraints
280: Map<String, Object> merged_constraints = new HashMap<String, Object>(
281: old_constrained_property.getConstraints());
282: merged_constraints.putAll(constrainedProperty
283: .getConstraints());
284: constrainedProperty.getConstraints().putAll(
285: merged_constraints);
286: }
287:
288: // add the validation rules of the new constrained property
289: List<PropertyValidationRule> rules = generateConstrainedPropertyRules(constrainedProperty);
290: for (PropertyValidationRule rule : rules) {
291: addConstrainedPropertyRule(constrainedProperty, rule);
292: }
293:
294: // register which constraint names are active
295: if (null == mActivePropertyConstraints) {
296: mActivePropertyConstraints = new HashSet<String>();
297: }
298: synchronized (mActivePropertyConstraints) {
299: mActivePropertyConstraints.addAll(constrainedProperty
300: .getConstraints().keySet());
301: }
302:
303: // register this validation object as the listener of future constraint additions to the property
304: constrainedProperty.addListener(this );
305:
306: // unregister this bean from the old constrained property
307: if (old_constrained_property != null) {
308: old_constrained_property.removeListener(this );
309: }
310:
311: return rules;
312: }
313:
314: public void addConstraint(P constrainedProperty) {
315: addConstrainedPropertyRules(constrainedProperty);
316: }
317:
318: public void addConstraint(B constrainedBean) {
319: if (null == constrainedBean) {
320: return;
321: }
322:
323: if (mConstrainedBean != null) {
324: HashMap<String, Object> merged_constraints = mConstrainedBean
325: .getConstraints();
326: merged_constraints.putAll(constrainedBean.getConstraints());
327: mConstrainedBean.getConstraints()
328: .putAll(merged_constraints);
329: }
330:
331: mConstrainedBean = constrainedBean;
332: }
333:
334: public void addValidationError(ValidationError newError) {
335: if (null == newError) {
336: return;
337: }
338:
339: if (null == mValidationErrors) {
340: mValidationErrors = new LinkedHashSet<ValidationError>();
341: }
342:
343: if (mErrorLimitedSubjects != null
344: && mErrorLimitedSubjects
345: .contains(newError.getSubject())
346: && !isSubjectValid(newError.getSubject())) {
347: return;
348: }
349:
350: // Handle the overridable errors.
351: ValidationError error_to_remove = null;
352: for (ValidationError error : mValidationErrors) {
353: if (error.getSubject().equals(newError.getSubject())) {
354: // If the new error is overridable, don't add it since there's already
355: // and error present for the same subject.
356: if (newError.isOverridable()) {
357: return;
358: }
359: // If an error is present that is overridable, remember it so that it
360: // can be removed when the new error is added.
361: else if (error.isOverridable()) {
362: error_to_remove = error;
363: break;
364: }
365: }
366: }
367:
368: if (error_to_remove != null) {
369: mValidationErrors.remove(error_to_remove);
370: }
371:
372: mValidationErrors.add(newError);
373: }
374:
375: public List<ValidationRule> getRules() {
376: ensureActivatedValidation();
377:
378: if (null == mValidationRules) {
379: mValidationRules = new ArrayList<ValidationRule>();
380: }
381:
382: return mValidationRules;
383: }
384:
385: public Collection<P> getConstrainedProperties() {
386: ensureActivatedValidation();
387:
388: if (null == mConstrainedProperties) {
389: mConstrainedProperties = new LinkedHashMap<String, P>();
390: }
391:
392: return mConstrainedProperties.values();
393: }
394:
395: public boolean hasPropertyConstraint(String name) {
396: if (null == mActivePropertyConstraints) {
397: return false;
398: }
399:
400: return mActivePropertyConstraints.contains(name);
401: }
402:
403: public P getConstrainedProperty(String propertyName) {
404: ensureActivatedValidation();
405:
406: if (null == propertyName || 0 == propertyName.length()
407: || null == mConstrainedProperties) {
408: return null;
409: }
410:
411: return mConstrainedProperties.get(propertyName);
412: }
413:
414: public Collection<ValidationGroup<P>> getGroups() {
415: ensureActivatedValidation();
416:
417: if (null == mValidationGroups) {
418: mValidationGroups = new HashMap<String, ValidationGroup<P>>();
419: }
420:
421: return mValidationGroups.values();
422: }
423:
424: public ValidationGroup<P> getGroup(String name) {
425: ensureActivatedValidation();
426:
427: if (null == name || 0 == name.length()
428: || null == mValidationGroups) {
429: return null;
430: }
431:
432: return mValidationGroups.get(name);
433: }
434:
435: public B getConstrainedBean() {
436: ensureActivatedValidation();
437:
438: return mConstrainedBean;
439: }
440:
441: private boolean validateSubjects(List<String> subjects) {
442: ensureActivatedValidation();
443:
444: if (mValidationRules != null && mValidationRules.size() > 0) {
445: for (ValidationRule rule : mValidationRules) {
446: if (subjects != null
447: && !subjects.contains(rule.getSubject())) {
448: continue;
449: }
450:
451: if (!rule.validate()) {
452: addValidationError(rule.getError());
453: }
454: }
455: }
456:
457: return 0 == countValidationErrors();
458: }
459:
460: public boolean validate() {
461: return validateSubjects(null);
462: }
463:
464: public boolean validate(ValidationContext context) {
465: if (context != null) {
466: context.validate(retrieveValidatedBean());
467: }
468:
469: validateSubjects(null);
470:
471: return 0 == countValidationErrors();
472: }
473:
474: public boolean validateGroup(String name) {
475: return validateGroup(name, null);
476: }
477:
478: public boolean validateGroup(String name, ValidationContext context) {
479: ensureActivatedValidation();
480:
481: if (null == name || null == mValidationGroups) {
482: return true;
483: }
484:
485: List<String> subjects = null;
486: ValidationGroup<P> group = mValidationGroups.get(name);
487: if (group != null) {
488: subjects = group.getSubjects();
489: }
490:
491: if (null == subjects) {
492: return true;
493: }
494:
495: if (context != null) {
496: context.validate(retrieveValidatedBean());
497: }
498:
499: return validateSubjects(subjects);
500: }
501:
502: public int countValidationErrors() {
503: if (null == mValidationErrors) {
504: return 0;
505: }
506:
507: return mValidationErrors.size();
508: }
509:
510: public void resetValidation() {
511: if (mValidationErrors != null) {
512: mValidationErrors = new LinkedHashSet<ValidationError>();
513: }
514: }
515:
516: public Set<ValidationError> getValidationErrors() {
517: if (null == mValidationErrors) {
518: mValidationErrors = new LinkedHashSet<ValidationError>();
519: }
520:
521: return mValidationErrors;
522: }
523:
524: public void replaceValidationErrors(Set<ValidationError> errors) {
525: mValidationErrors = errors;
526: }
527:
528: public void limitSubjectErrors(String subject) {
529: if (null == subject) {
530: return;
531: }
532:
533: if (null == mErrorLimitedSubjects) {
534: mErrorLimitedSubjects = new ArrayList<String>();
535: }
536:
537: if (!mErrorLimitedSubjects.contains(subject)) {
538: mErrorLimitedSubjects.add(subject);
539: }
540: }
541:
542: public void unlimitSubjectErrors(String subject) {
543: if (null == subject) {
544: return;
545: }
546:
547: if (null == mErrorLimitedSubjects) {
548: return;
549: }
550:
551: mErrorLimitedSubjects.remove(subject);
552: }
553:
554: public boolean isSubjectValid(String subject) {
555: if (null == subject) {
556: return true;
557: }
558:
559: if (null == mValidationErrors) {
560: return true;
561: }
562:
563: boolean valid = true;
564:
565: for (ValidationError error : mValidationErrors) {
566: if (error.getSubject().equals(subject)) {
567: valid = false;
568: break;
569: }
570: }
571:
572: return valid;
573: }
574:
575: public void makeSubjectValid(String subject) {
576: if (null == subject) {
577: return;
578: }
579:
580: if (null == mValidationErrors) {
581: return;
582: }
583:
584: ArrayList<ValidationError> errors_to_remove = new ArrayList<ValidationError>();
585:
586: for (ValidationError error : mValidationErrors) {
587: if (error.getSubject().equals(subject)) {
588: errors_to_remove.add(error);
589: }
590: }
591:
592: for (ValidationError error_to_remove : errors_to_remove) {
593: mValidationErrors.remove(error_to_remove);
594: }
595: }
596:
597: public void makeErrorValid(String identifier, String subject) {
598: if (null == subject) {
599: return;
600: }
601:
602: if (null == identifier) {
603: return;
604: }
605:
606: if (null == mValidationErrors) {
607: return;
608: }
609:
610: ArrayList<ValidationError> errors_to_remove = new ArrayList<ValidationError>();
611:
612: for (ValidationError error : mValidationErrors) {
613: if (error.getSubject().equals(subject)
614: && error.getIdentifier().equals(identifier)) {
615: errors_to_remove.add(error);
616: }
617: }
618:
619: for (ValidationError error_to_remove : errors_to_remove) {
620: mValidationErrors.remove(error_to_remove);
621: }
622: }
623:
624: public List<String> getValidatedSubjects() {
625: ensureActivatedValidation();
626:
627: if (null == mValidatedSubjects) {
628: mValidatedSubjects = new ArrayList<String>();
629: }
630:
631: return mValidatedSubjects;
632: }
633:
634: public static String getErrorIndication(Validated validated,
635: String subject, String valid, String error) {
636: if (null != validated && !validated.isSubjectValid(subject)) {
637: return error;
638: } else {
639: return valid;
640: }
641: }
642:
643: public Collection<String> getLoadingErrors(String propertyName) {
644: if (null == propertyName) {
645: return null;
646: }
647:
648: for (ValidationRule rule : getRules()) {
649: if (rule instanceof PropertyValidationRule) {
650: PropertyValidationRule property_rule = (PropertyValidationRule) rule;
651: if (propertyName
652: .equals(property_rule.getPropertyName())
653: && property_rule.getLoadingErrors() != null
654: && property_rule.getLoadingErrors().size() > 0) {
655: return property_rule.getLoadingErrors();
656: }
657: }
658: }
659:
660: return null;
661: }
662:
663: public void constraintSet(ConstrainedProperty property,
664: String name, Object constraintData) {
665: if (null == mActivePropertyConstraints) {
666: mActivePropertyConstraints = new HashSet<String>();
667: }
668: mActivePropertyConstraints.add(name);
669: }
670:
671: public Object clone() throws CloneNotSupportedException {
672: Validation new_validation = null;
673: try {
674: new_validation = (Validation) super .clone();
675:
676: if (mValidationRules != null) {
677: new_validation.mValidationRules = ObjectUtils
678: .deepClone(mValidationRules);
679: for (ValidationRule rule : (ArrayList<ValidationRule>) new_validation.mValidationRules) {
680: if (this == rule.getBean()) {
681: rule.setBean(new_validation);
682: }
683: }
684: }
685:
686: if (this == new_validation.mValidatedBean) {
687: new_validation.mValidatedBean = new_validation;
688: }
689:
690: if (mValidationErrors != null) {
691: new_validation.mValidationErrors = new LinkedHashSet<ValidationError>(
692: mValidationErrors);
693: }
694:
695: if (mConstrainedProperties != null) {
696: new_validation.mConstrainedProperties = new LinkedHashMap<String, P>();
697: for (Map.Entry<String, P> entry_property : mConstrainedProperties
698: .entrySet()) {
699: new_validation.mConstrainedProperties.put(
700: entry_property.getKey(), entry_property
701: .getValue());
702: new_validation.addConstraint(entry_property
703: .getValue().clone());
704: }
705: }
706:
707: if (mActivePropertyConstraints != null) {
708: new_validation.mActivePropertyConstraints = new HashSet<String>(
709: mActivePropertyConstraints);
710: }
711:
712: if (mErrorLimitedSubjects != null) {
713: new_validation.mErrorLimitedSubjects = new ArrayList<String>(
714: mErrorLimitedSubjects);
715: }
716:
717: if (mValidationGroups != null) {
718: new_validation.mValidationGroups = new HashMap<String, ValidationGroup<P>>();
719: ValidationGroup<P> new_group;
720: for (ValidationGroup<P> group : mValidationGroups
721: .values()) {
722: new_group = group.clone();
723: new_group.setValidation(new_validation);
724: new_validation.mValidationGroups.put(new_group
725: .getName(), new_group);
726: }
727: }
728: } catch (CloneNotSupportedException e) {
729: ///CLOVER:OFF
730: // this should never happen
731: Logger.getLogger("com.uwyn.rife.site").severe(
732: ExceptionUtils.getExceptionStackTrace(e));
733: ///CLOVER:ON
734: }
735:
736: return new_validation;
737: }
738: }
|