1 //$Id: ClassValidator.java 15133 2008-08-20 10:05:57Z hardy.ferentschik $
2 package org.hibernate.validator;
3
4 import java.beans.Introspector;
5 import java.io.IOException;
6 import java.io.ObjectInputStream;
7 import java.io.ObjectOutputStream;
8 import java.io.Serializable;
9 import java.lang.annotation.Annotation;
10 import java.lang.reflect.Method;
11 import java.lang.reflect.Modifier;
12 import java.text.MessageFormat;
13 import java.util.ArrayList;
14 import java.util.Collection;
15 import java.util.HashMap;
16 import java.util.HashSet;
17 import java.util.Iterator;
18 import java.util.List;
19 import java.util.Locale;
20 import java.util.Map;
21 import java.util.MissingResourceException;
22 import java.util.ResourceBundle;
23 import java.util.Set;
24 import java.util.StringTokenizer;
25
26 import org.hibernate.AssertionFailure;
27 import org.hibernate.Hibernate;
28 import org.hibernate.MappingException;
29 import org.hibernate.mapping.PersistentClass;
30 import org.hibernate.mapping.Property;
31 import org.hibernate.mapping.Component;
32 import org.hibernate.annotations.common.reflection.Filter;
33 import org.hibernate.annotations.common.reflection.ReflectionManager;
34 import org.hibernate.annotations.common.reflection.XClass;
35 import org.hibernate.annotations.common.reflection.XMember;
36 import org.hibernate.annotations.common.reflection.XMethod;
37 import org.hibernate.annotations.common.reflection.XProperty;
38 import org.hibernate.annotations.common.reflection.java.JavaReflectionManager;
39 import org.hibernate.util.IdentitySet;
40 import org.hibernate.validator.interpolator.DefaultMessageInterpolatorAggregator;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
43
44
45 /**
46 * Engine that take a bean and check every expressed annotation restrictions
47 *
48 * @author Gavin King
49 * @author Emmanuel Bernard
50 */
51 public class ClassValidator<T> implements Serializable {
52 //TODO Define magic number
53 private static final Logger log = LoggerFactory.getLogger( ClassValidator.class );
54 private static final InvalidValue[] EMPTY_INVALID_VALUE_ARRAY = new InvalidValue[]{};
55 private static final String DEFAULT_VALIDATOR_MESSAGE = "org.hibernate.validator.resources.DefaultValidatorMessages";
56 private static final String VALIDATOR_MESSAGE = "ValidatorMessages";
57 private static final Set<Class> INDEXABLE_CLASS = new HashSet<Class>();
58
59 static {
60 INDEXABLE_CLASS.add( Integer.class );
61 INDEXABLE_CLASS.add( Long.class );
62 INDEXABLE_CLASS.add( String.class );
63 }
64
65 static {
66 Version.touch(); //touch version
67 }
68
69 private final Class<T> beanClass;
70 private transient ResourceBundle messageBundle;
71 private transient ResourceBundle defaultMessageBundle;
72 private transient boolean isUserProvidedResourceBundle;
73 private transient ReflectionManager reflectionManager;
74
75 private final transient Map<XClass, ClassValidator> childClassValidators;
76 private transient List<Validator> beanValidators;
77 private transient List<Validator> memberValidators;
78 private transient List<XMember> memberGetters;
79 private transient List<XMember> childGetters;
80 private transient DefaultMessageInterpolatorAggregator defaultInterpolator;
81 private transient MessageInterpolator userInterpolator;
82 private static final Filter GET_ALL_FILTER = new Filter() {
83 public boolean returnStatic() {
84 return true;
85 }
86
87 public boolean returnTransient() {
88 return true;
89 }
90 };
91
92 /**
93 * create the validator engine for this bean type
94 */
95 public ClassValidator(Class<T> beanClass) {
96 this( beanClass, (ResourceBundle) null );
97 }
98
99 /**
100 * create the validator engine for a particular bean class, using a resource bundle
101 * for message rendering on violation
102 */
103 public ClassValidator(Class<T> beanClass, ResourceBundle resourceBundle) {
104 this( beanClass, resourceBundle, null, new HashMap<XClass, ClassValidator>(), null );
105 }
106
107 /**
108 * create the validator engine for a particular bean class, using a custom message interpolator
109 * for message rendering on violation
110 */
111 public ClassValidator(Class<T> beanClass, MessageInterpolator interpolator) {
112 this( beanClass, null, interpolator, new HashMap<XClass, ClassValidator>(), null );
113 }
114
115 /**
116 * Not a public API
117 */
118 public ClassValidator(
119 Class<T> beanClass, ResourceBundle resourceBundle, MessageInterpolator interpolator,
120 Map<XClass, ClassValidator> childClassValidators, ReflectionManager reflectionManager
121 ) {
122 this.reflectionManager = reflectionManager != null ? reflectionManager : new JavaReflectionManager();
123 XClass beanXClass = this.reflectionManager.toXClass( beanClass );
124 this.beanClass = beanClass;
125 this.messageBundle = resourceBundle == null ?
126 getDefaultResourceBundle() :
127 resourceBundle;
128 this.defaultMessageBundle = ResourceBundle.getBundle( DEFAULT_VALIDATOR_MESSAGE );
129 this.userInterpolator = interpolator;
130 this.childClassValidators = childClassValidators != null ?
131 childClassValidators :
132 new HashMap<XClass, ClassValidator>();
133 initValidator( beanXClass, this.childClassValidators );
134 }
135
136 @SuppressWarnings("unchecked")
137 protected ClassValidator(
138 XClass beanXClass, ResourceBundle resourceBundle, MessageInterpolator userInterpolator,
139 Map<XClass, ClassValidator> childClassValidators, ReflectionManager reflectionManager
140 ) {
141 this.reflectionManager = reflectionManager;
142 this.beanClass = reflectionManager.toClass( beanXClass );
143 this.messageBundle = resourceBundle == null ?
144 getDefaultResourceBundle() :
145 resourceBundle;
146 this.defaultMessageBundle = ResourceBundle.getBundle( DEFAULT_VALIDATOR_MESSAGE );
147 this.userInterpolator = userInterpolator;
148 this.childClassValidators = childClassValidators;
149 initValidator( beanXClass, childClassValidators );
150 }
151
152 private ResourceBundle getDefaultResourceBundle() {
153 ResourceBundle rb;
154 try {
155 //use context class loader as a first citizen
156 ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
157 if ( contextClassLoader == null ) {
158 throw new MissingResourceException( "No context classloader", null, VALIDATOR_MESSAGE );
159 }
160 rb = ResourceBundle.getBundle(
161 VALIDATOR_MESSAGE,
162 Locale.getDefault(),
163 contextClassLoader
164 );
165 }
166 catch (MissingResourceException e) {
167 log.trace( "ResourceBundle {} not found in thread context classloader", VALIDATOR_MESSAGE );
168 //then use the Validator Framework classloader
169 try {
170 rb = ResourceBundle.getBundle(
171 VALIDATOR_MESSAGE,
172 Locale.getDefault(),
173 this.getClass().getClassLoader()
174 );
175 }
176 catch (MissingResourceException ee) {
177 log.debug(
178 "ResourceBundle ValidatorMessages not found in Validator classloader. Delegate to {}",
179 DEFAULT_VALIDATOR_MESSAGE
180 );
181 //the user did not override the default ValidatorMessages
182 rb = null;
183 }
184 }
185 isUserProvidedResourceBundle = true;
186 return rb;
187 }
188
189 private void initValidator(
190 XClass xClass, Map<XClass, ClassValidator> childClassValidators
191 ) {
192 beanValidators = new ArrayList<Validator>();
193 memberValidators = new ArrayList<Validator>();
194 memberGetters = new ArrayList<XMember>();
195 childGetters = new ArrayList<XMember>();
196 defaultInterpolator = new DefaultMessageInterpolatorAggregator();
197 defaultInterpolator.initialize( messageBundle, defaultMessageBundle );
198
199 //build the class hierarchy to look for members in
200 childClassValidators.put( xClass, this );
201 Collection<XClass> classes = new HashSet<XClass>();
202 addSuperClassesAndInterfaces( xClass, classes );
203 for ( XClass currentClass : classes ) {
204 Annotation[] classAnnotations = currentClass.getAnnotations();
205 for ( int i = 0; i < classAnnotations.length ; i++ ) {
206 Annotation classAnnotation = classAnnotations[i];
207 Validator beanValidator = createValidator( classAnnotation );
208 if ( beanValidator != null ) beanValidators.add( beanValidator );
209 handleAggregateAnnotations(classAnnotation, null);
210 }
211 }
212
213 //Check on all selected classes
214 for ( XClass currClass : classes ) {
215 List<XMethod> methods = currClass.getDeclaredMethods();
216 for ( XMethod method : methods ) {
217 createMemberValidator( method );
218 createChildValidator( method );
219 }
220
221 List<XProperty> fields = currClass.getDeclaredProperties(
222 "field", GET_ALL_FILTER
223 );
224 for ( XProperty field : fields ) {
225 createMemberValidator( field );
226 createChildValidator( field );
227 }
228 }
229 }
230
231 private void addSuperClassesAndInterfaces(XClass clazz, Collection<XClass> classes) {
232 for ( XClass currClass = clazz; currClass != null ; currClass = currClass.getSuperclass() ) {
233 if ( ! classes.add( currClass ) ) return;
234 XClass[] interfaces = currClass.getInterfaces();
235 for ( XClass interf : interfaces ) {
236 addSuperClassesAndInterfaces( interf, classes );
237 }
238 }
239 }
240
241 private boolean handleAggregateAnnotations(Annotation annotation, XMember member) {
242 Object[] values;
243 try {
244 Method valueMethod = annotation.getClass().getMethod( "value" );
245 if ( valueMethod.getReturnType().isArray() ) {
246 values = (Object[]) valueMethod.invoke( annotation );
247 }
248 else {
249 return false;
250 }
251 }
252 catch (NoSuchMethodException e) {
253 return false;
254 }
255 catch (Exception e) {
256 throw new IllegalStateException( e );
257 }
258
259 boolean validatorPresent = false;
260 for ( Object value : values ) {
261 if ( value instanceof Annotation ) {
262 annotation = (Annotation) value;
263 Validator validator = createValidator( annotation );
264 if ( validator != null ) {
265 if ( member != null ) {
266 //member
267 memberValidators.add( validator );
268 setAccessible( member );
269 memberGetters.add( member );
270 }
271 else {
272 //bean
273 beanValidators.add( validator );
274 }
275 validatorPresent = true;
276 }
277 }
278 }
279 return validatorPresent;
280 }
281
282 @SuppressWarnings("unchecked")
283 private void createChildValidator( XMember member) {
284 if ( member.isAnnotationPresent( Valid.class ) ) {
285 setAccessible( member );
286 childGetters.add( member );
287 XClass clazz;
288 if ( member.isCollection() || member.isArray() ) {
289 clazz = member.getElementClass();
290 }
291 else {
292 clazz = member.getType();
293 }
294 if ( !childClassValidators.containsKey( clazz ) ) {
295 //ClassValidator added by side effect (added to childClassValidators during CV construction)
296 new ClassValidator( clazz, messageBundle, userInterpolator, childClassValidators, reflectionManager );
297 }
298 }
299 }
300
301 private void createMemberValidator(XMember member) {
302 boolean validatorPresent = false;
303 Annotation[] memberAnnotations = member.getAnnotations();
304 for ( Annotation methodAnnotation : memberAnnotations ) {
305 Validator propertyValidator = createValidator( methodAnnotation );
306 if ( propertyValidator != null ) {
307 memberValidators.add( propertyValidator );
308 setAccessible( member );
309 memberGetters.add( member );
310 validatorPresent = true;
311 }
312 boolean agrValidPresent = handleAggregateAnnotations( methodAnnotation, member );
313 validatorPresent = validatorPresent || agrValidPresent;
314 }
315 if ( validatorPresent && !member.isTypeResolved() ) {
316 log.warn( "Original type of property {} is unbound and has been approximated.", member );
317 }
318 }
319
320 private static void setAccessible(XMember member) {
321 if ( !Modifier.isPublic( member.getModifiers() ) ) {
322 member.setAccessible( true );
323 }
324 }
325
326 @SuppressWarnings("unchecked")
327 private Validator createValidator(Annotation annotation) {
328 try {
329 ValidatorClass validatorClass = annotation.annotationType().getAnnotation( ValidatorClass.class );
330 if ( validatorClass == null ) {
331 return null;
332 }
333 Validator beanValidator = validatorClass.value().newInstance();
334 beanValidator.initialize( annotation );
335 defaultInterpolator.addInterpolator( annotation, beanValidator );
336 return beanValidator;
337 }
338 catch (Exception e) {
339 throw new IllegalArgumentException( "could not instantiate ClassValidator", e );
340 }
341 }
342
343 public boolean hasValidationRules() {
344 return beanValidators.size() != 0 || memberValidators.size() != 0;
345 }
346
347 /**
348 * apply constraints on a bean instance and return all the failures.
349 * if <code>bean</code> is null, an empty array is returned
350 */
351 public InvalidValue[] getInvalidValues(T bean) {
352 return this.getInvalidValues( bean, new IdentitySet() );
353 }
354
355 /**
356 * apply constraints on a bean instance and return all the failures.
357 * if <code>bean</code> is null, an empty array is returned
358 */
359 @SuppressWarnings("unchecked")
360 protected InvalidValue[] getInvalidValues(T bean, Set<Object> circularityState) {
361 if ( bean == null || circularityState.contains( bean ) ) {
362 return EMPTY_INVALID_VALUE_ARRAY; //Avoid circularity
363 }
364 else {
365 circularityState.add( bean );
366 }
367
368 if ( !beanClass.isInstance( bean ) ) {
369 throw new IllegalArgumentException( "not an instance of: " + bean.getClass() );
370 }
371
372 List<InvalidValue> results = new ArrayList<InvalidValue>();
373
374 for ( int i = 0; i < beanValidators.size() ; i++ ) {
375 Validator validator = beanValidators.get( i );
376 if ( !validator.isValid( bean ) ) {
377 results.add( new InvalidValue( interpolate(validator), beanClass, null, bean, bean ) );
378 }
379 }
380
381 for ( int i = 0; i < memberValidators.size() ; i++ ) {
382 XMember getter = memberGetters.get( i );
383 if ( Hibernate.isPropertyInitialized( bean, getPropertyName( getter ) ) ) {
384 Object value = getMemberValue( bean, getter );
385 Validator validator = memberValidators.get( i );
386 if ( !validator.isValid( value ) ) {
387 String propertyName = getPropertyName( getter );
388 results.add( new InvalidValue( interpolate(validator), beanClass, propertyName, value, bean ) );
389 }
390 }
391 }
392
393 for ( int i = 0; i < childGetters.size() ; i++ ) {
394 XMember getter = childGetters.get( i );
395 if ( Hibernate.isPropertyInitialized( bean, getPropertyName( getter ) ) ) {
396 Object value = getMemberValue( bean, getter );
397 if ( value != null && Hibernate.isInitialized( value ) ) {
398 String propertyName = getPropertyName( getter );
399 if ( getter.isCollection() ) {
400 int index = 0;
401 boolean isIterable = value instanceof Iterable;
402 Map map = ! isIterable ? (Map) value : null;
403 Iterable elements = isIterable ?
404 (Iterable) value :
405 map.keySet();
406 for ( Object element : elements ) {
407 Object actualElement = isIterable ? element : map.get( element );
408 if ( actualElement == null ) {
409 index++;
410 continue;
411 }
412 InvalidValue[] invalidValues = getClassValidator( actualElement )
413 .getInvalidValues( actualElement, circularityState );
414
415 String indexedPropName = MessageFormat.format(
416 "{0}[{1}]",
417 propertyName,
418 INDEXABLE_CLASS.contains( element.getClass() ) ?
419 ( "'" + element + "'" ) :
420 index
421 );
422 index++;
423
424 for ( InvalidValue invalidValue : invalidValues ) {
425 invalidValue.addParentBean( bean, indexedPropName );
426 results.add( invalidValue );
427 }
428 }
429 }
430 if ( getter.isArray() ) {
431 int index = 0;
432 for ( Object element : (Object[]) value ) {
433 if ( element == null ) {
434 index++;
435 continue;
436 }
437 InvalidValue[] invalidValues = getClassValidator( element )
438 .getInvalidValues( element, circularityState );
439
440 String indexedPropName = MessageFormat.format(
441 "{0}[{1}]",
442 propertyName,
443 index
444 );
445 index++;
446
447 for ( InvalidValue invalidValue : invalidValues ) {
448 invalidValue.addParentBean( bean, indexedPropName );
449 results.add( invalidValue );
450 }
451 }
452 }
453 else {
454 InvalidValue[] invalidValues = getClassValidator( value )
455 .getInvalidValues( value, circularityState );
456 for ( InvalidValue invalidValue : invalidValues ) {
457 invalidValue.addParentBean( bean, propertyName );
458 results.add( invalidValue );
459 }
460 }
461 }
462 }
463 }
464
465 return results.toArray( new InvalidValue[results.size()] );
466 }
467
468 private String interpolate(Validator validator) {
469 String message = defaultInterpolator.getAnnotationMessage( validator );
470 if (userInterpolator != null) {
471 return userInterpolator.interpolate( message, validator, defaultInterpolator );
472 }
473 else {
474 return defaultInterpolator.interpolate( message, validator, null);
475 }
476 }
477
478 @SuppressWarnings("unchecked")
479 private ClassValidator getClassValidator(Object value) {
480 Class clazz = value.getClass();
481 ClassValidator validator = childClassValidators.get( reflectionManager.toXClass( clazz ) );
482 if ( validator == null ) { //handles polymorphism
483 //TODO cache this thing. in a second queue (reflectionManager being sealed)? beware of concurrency
484 validator = new ClassValidator( clazz );
485 }
486 return validator;
487 }
488
489 /**
490 * Apply constraints of a particular property on a bean instance and return all the failures.
491 * Note this is not recursive.
492 */
493 //TODO should it be recursive?
494 public InvalidValue[] getInvalidValues(T bean, String propertyName) {
495 List<InvalidValue> results = new ArrayList<InvalidValue>();
496
497 for ( int i = 0; i < memberValidators.size() ; i++ ) {
498 XMember getter = memberGetters.get( i );
499 if ( getPropertyName( getter ).equals( propertyName ) ) {
500 Object value = getMemberValue( bean, getter );
501 Validator validator = memberValidators.get( i );
502 if ( !validator.isValid( value ) ) {
503 results.add( new InvalidValue( interpolate(validator), beanClass, propertyName, value, bean ) );
504 }
505 }
506 }
507
508 return results.toArray( new InvalidValue[results.size()] );
509 }
510
511 /**
512 * Apply constraints of a particular property value of a bean type and return all the failures.
513 * The InvalidValue objects returns return null for InvalidValue#getBean() and InvalidValue#getRootBean()
514 * Note this is not recursive.
515 */
516 //TODO should it be recursive?
517 public InvalidValue[] getPotentialInvalidValues(String propertyName, Object value) {
518 List<InvalidValue> results = new ArrayList<InvalidValue>();
519
520 for ( int i = 0; i < memberValidators.size() ; i++ ) {
521 XMember getter = memberGetters.get( i );
522 if ( getPropertyName( getter ).equals( propertyName ) ) {
523 Validator validator = memberValidators.get( i );
524 if ( !validator.isValid( value ) ) {
525 results.add( new InvalidValue( interpolate(validator), beanClass, propertyName, value, null ) );
526 }
527 }
528 }
529
530 return results.toArray( new InvalidValue[results.size()] );
531 }
532
533 private Object getMemberValue(T bean, XMember getter) {
534 Object value;
535 try {
536 value = getter.invoke( bean );
537 }
538 catch (Exception e) {
539 throw new IllegalStateException( "Could not get property value", e );
540 }
541 return value;
542 }
543
544 private String getPropertyName(XMember member) {
545 //Do no try to cache the result in a map, it's actually much slower (2.x time)
546 String propertyName;
547 if ( XProperty.class.isAssignableFrom( member.getClass() ) ) {
548 propertyName = member.getName();
549 }
550 else if ( XMethod.class.isAssignableFrom( member.getClass() ) ) {
551 propertyName = member.getName();
552 if ( propertyName.startsWith( "is" ) ) {
553 propertyName = Introspector.decapitalize( propertyName.substring( 2 ) );
554 }
555 else if ( propertyName.startsWith( "get" ) ) {
556 propertyName = Introspector.decapitalize( propertyName.substring( 3 ) );
557 }
558 //do nothing for non getter method, in case someone want to validate a PO Method
559 }
560 else {
561 throw new AssertionFailure( "Unexpected member: " + member.getClass().getName() );
562 }
563 return propertyName;
564 }
565
566 /** @deprecated */
567 private String replace(String message, Annotation parameters) {
568 StringTokenizer tokens = new StringTokenizer( message, "#{}", true );
569 StringBuilder buf = new StringBuilder( 30 );
570 boolean escaped = false;
571 boolean el = false;
572 while ( tokens.hasMoreTokens() ) {
573 String token = tokens.nextToken();
574 if ( !escaped && "#".equals( token ) ) {
575 el = true;
576 }
577 if ( !el && "{".equals( token ) ) {
578 escaped = true;
579 }
580 else if ( escaped && "}".equals( token ) ) {
581 escaped = false;
582 }
583 else if ( !escaped ) {
584 if ( "{".equals( token ) ) el = false;
585 buf.append( token );
586 }
587 else {
588 Method member;
589 try {
590 member = parameters.getClass().getMethod( token, (Class[]) null );
591 }
592 catch (NoSuchMethodException nsfme) {
593 member = null;
594 }
595 if ( member != null ) {
596 try {
597 buf.append( member.invoke( parameters ) );
598 }
599 catch (Exception e) {
600 throw new IllegalArgumentException( "could not render message", e );
601 }
602 }
603 else {
604 String string = null;
605 try {
606 string = messageBundle != null ? messageBundle.getString( token ) : null;
607 }
608 catch( MissingResourceException e ) {
609 //give a second chance with the default resource bundle
610 }
611 if (string == null) {
612 try {
613 string = defaultMessageBundle.getString( token );
614 }
615 catch( MissingResourceException e) {
616 throw new MissingResourceException(
617 "Can't find resource in validator bundles, key " + token,
618 defaultMessageBundle.getClass().getName(),
619 token
620 );
621 }
622 }
623 if ( string != null ) buf.append( replace( string, parameters ) );
624 }
625 }
626 }
627 return buf.toString();
628 }
629
630 /**
631 * apply the registred constraints rules on the hibernate metadata (to be applied on DB schema...)
632 *
633 * @param persistentClass hibernate metadata
634 */
635 public void apply(PersistentClass persistentClass) {
636
637 for ( Validator validator : beanValidators ) {
638 if ( validator instanceof PersistentClassConstraint ) {
639 ( (PersistentClassConstraint) validator ).apply( persistentClass );
640 }
641 }
642
643 Iterator<Validator> validators = memberValidators.iterator();
644 Iterator<XMember> getters = memberGetters.iterator();
645 while ( validators.hasNext() ) {
646 Validator validator = validators.next();
647 String propertyName = getPropertyName( getters.next() );
648 if ( validator instanceof PropertyConstraint ) {
649 try {
650 Property property = findPropertyByName(persistentClass, propertyName);
651 if (property != null) {
652 ( (PropertyConstraint) validator ).apply( property );
653 }
654 }
655 catch (MappingException pnfe) {
656 //do nothing
657 }
658 }
659 }
660
661 }
662
663 public void assertValid(T bean) {
664 InvalidValue[] values = getInvalidValues( bean );
665 if ( values.length > 0 ) {
666 throw new InvalidStateException( values );
667 }
668 }
669
670 private void writeObject(ObjectOutputStream oos) throws IOException {
671 ResourceBundle rb = messageBundle;
672 MessageInterpolator interpolator = this.userInterpolator;
673 if ( rb != null && ! ( rb instanceof Serializable ) ) {
674 messageBundle = null;
675 if ( ! isUserProvidedResourceBundle ) {
676 log.warn(
677 "Serializing a ClassValidator with a non serializable ResourceBundle: ResourceBundle ignored"
678 );
679 }
680 }
681 if (interpolator != null && ! (interpolator instanceof Serializable) ) {
682 userInterpolator = null;
683 log.warn( "Serializing a non serializable MessageInterpolator" );
684 }
685 oos.defaultWriteObject();
686 oos.writeObject( messageBundle );
687 oos.writeObject( userInterpolator );
688 messageBundle = rb;
689 userInterpolator = interpolator;
690 }
691
692 private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
693 ois.defaultReadObject();
694 ResourceBundle rb = (ResourceBundle) ois.readObject();
695 if ( rb == null ) rb = getDefaultResourceBundle();
696 this.messageBundle = rb;
697 this.userInterpolator = (MessageInterpolator) ois.readObject();
698 this.defaultMessageBundle = ResourceBundle.getBundle( DEFAULT_VALIDATOR_MESSAGE );
699 reflectionManager = new JavaReflectionManager();
700 initValidator( reflectionManager.toXClass( beanClass ), new HashMap<XClass, ClassValidator>() );
701 }
702
703 /**
704 * Retrieve the property by path in a recursive way, including IndetifierProperty in the loop
705 * If propertyName is null or empty, the IdentifierProperty is returned
706 */
707 public static Property findPropertyByName(PersistentClass associatedClass, String propertyName) {
708 Property property = null;
709 Property idProperty = associatedClass.getIdentifierProperty();
710 String idName = idProperty != null ? idProperty.getName() : null;
711 try {
712 if ( propertyName == null
713 || propertyName.length() == 0
714 || propertyName.equals( idName ) ) {
715 //default to id
716 property = idProperty;
717 }
718 else {
719 if ( propertyName.indexOf( idName + "." ) == 0 ) {
720 property = idProperty;
721 propertyName = propertyName.substring( idName.length() + 1 );
722 }
723 StringTokenizer st = new StringTokenizer( propertyName, ".", false );
724 while ( st.hasMoreElements() ) {
725 String element = (String) st.nextElement();
726 if ( property == null ) {
727 property = associatedClass.getProperty( element );
728 }
729 else {
730 if ( ! property.isComposite() ) return null;
731 property = ( (Component) property.getValue() ).getProperty( element );
732 }
733 }
734 }
735 }
736 catch (MappingException e) {
737 try {
738 //if we do not find it try to check the identifier mapper
739 if ( associatedClass.getIdentifierMapper() == null ) return null;
740 StringTokenizer st = new StringTokenizer( propertyName, ".", false );
741 while ( st.hasMoreElements() ) {
742 String element = (String) st.nextElement();
743 if ( property == null ) {
744 property = associatedClass.getIdentifierMapper().getProperty( element );
745 }
746 else {
747 if ( ! property.isComposite() ) return null;
748 property = ( (Component) property.getValue() ).getProperty( element );
749 }
750 }
751 }
752 catch (MappingException ee) {
753 return null;
754 }
755 }
756 return property;
757 }
758 }