001    // Copyright 2010 The Apache Software Foundation
002    //
003    // Licensed under the Apache License, Version 2.0 (the "License");
004    // you may not use this file except in compliance with the License.
005    // You may obtain a copy of the License at
006    //
007    //     http://www.apache.org/licenses/LICENSE-2.0
008    //
009    // Unless required by applicable law or agreed to in writing, software
010    // distributed under the License is distributed on an "AS IS" BASIS,
011    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012    // See the License for the specific language governing permissions and
013    // limitations under the License.
014    package org.apache.tapestry5.internal.beanvalidator;
015    
016    import static java.lang.String.format;
017    
018    import java.lang.annotation.Annotation;
019    import java.util.Iterator;
020    import java.util.Set;
021    
022    import javax.validation.ConstraintViolation;
023    import javax.validation.MessageInterpolator;
024    import javax.validation.Validator;
025    import javax.validation.ValidatorFactory;
026    import javax.validation.MessageInterpolator.Context;
027    import javax.validation.metadata.BeanDescriptor;
028    import javax.validation.metadata.ConstraintDescriptor;
029    import javax.validation.metadata.PropertyDescriptor;
030    
031    import org.apache.tapestry5.Field;
032    import org.apache.tapestry5.FieldValidator;
033    import org.apache.tapestry5.MarkupWriter;
034    import org.apache.tapestry5.ValidationException;
035    import org.apache.tapestry5.beanvalidator.BeanValidatorGroupSource;
036    import org.apache.tapestry5.beanvalidator.ClientConstraintDescriptor;
037    import org.apache.tapestry5.beanvalidator.ClientConstraintDescriptorSource;
038    import org.apache.tapestry5.internal.BeanValidationContext;
039    import org.apache.tapestry5.json.JSONObject;
040    import org.apache.tapestry5.services.Environment;
041    import org.apache.tapestry5.services.FormSupport;
042    
043    
044    public class BeanFieldValidator implements FieldValidator
045    {
046            private final Field field;
047            private final ValidatorFactory validatorFactory;
048            private final BeanValidatorGroupSource beanValidationGroupSource;
049            private final ClientConstraintDescriptorSource clientValidatorSource;
050            private final FormSupport formSupport;
051            private final Environment environment;
052            
053            public BeanFieldValidator(Field field,
054                            ValidatorFactory validatorFactory,
055                            BeanValidatorGroupSource beanValidationGroupSource,
056                            ClientConstraintDescriptorSource clientValidatorSource,
057                            FormSupport formSupport,
058                            Environment environment) 
059            {
060                    this.field = field;
061                    this.validatorFactory = validatorFactory;
062                    this.beanValidationGroupSource = beanValidationGroupSource;
063                    this.clientValidatorSource = clientValidatorSource;
064                    this.formSupport = formSupport;
065                    this.environment = environment;
066            }
067            
068            public boolean isRequired() 
069            {
070                    return false;
071            }
072    
073            public void render(final MarkupWriter writer) 
074            {
075                    final BeanValidationContext beanValidationContext = environment.peek(BeanValidationContext.class);
076    
077                    if (beanValidationContext == null) 
078                    {
079                            return;
080                    }
081                    
082                    final Validator validator = validatorFactory.getValidator();
083                    
084                    BeanDescriptor beanDescriptor = validator.getConstraintsForClass(beanValidationContext.getBeanType());
085                    
086                    String currentProperty = beanValidationContext.getCurrentProperty();
087                    
088                    if(currentProperty == null) return;
089                    
090                    PropertyDescriptor propertyDescriptor = beanDescriptor.getConstraintsForProperty(currentProperty);
091                    
092                    if(propertyDescriptor == null) return;
093                    
094                    for (final ConstraintDescriptor<?> descriptor :propertyDescriptor.getConstraintDescriptors()) 
095                    {
096                            Class<? extends Annotation> annotationType = descriptor.getAnnotation().annotationType();
097                            
098                            ClientConstraintDescriptor clientConstraintDescriptor = clientValidatorSource.getConstraintDescriptor(annotationType);
099                            
100                            if(clientConstraintDescriptor != null)
101                            {       
102                                    String message = format("%s %s", field.getLabel(), interpolateMessage(descriptor));
103                                    
104                                    JSONObject specs = new JSONObject();
105                                    
106                    for (String attribute : clientConstraintDescriptor.getAttributes()) 
107                    {
108                        Object object = descriptor.getAttributes().get(attribute);
109                        
110                        if (object == null) 
111                        {
112                          throw new RuntimeException("Expected attribute is null");
113                        }
114                        specs.put(attribute, object);
115                    }
116                    
117                                    formSupport.addValidation(field, clientConstraintDescriptor.getValidatorName(), message, specs);
118                            }
119                    }
120            }
121    
122            @SuppressWarnings("unchecked")
123            public void validate(final Object value) throws ValidationException 
124            {
125    
126                    final BeanValidationContext beanValidationContext = environment.peek(BeanValidationContext.class);
127    
128                    if (beanValidationContext == null) 
129                    {
130                            return;
131                    }
132                    
133                    final Validator validator = validatorFactory.getValidator();
134                    
135                    String currentProperty = beanValidationContext.getCurrentProperty();
136                    
137                    if(currentProperty == null) return;
138                    
139                    BeanDescriptor beanDescriptor = validator.getConstraintsForClass(beanValidationContext.getBeanType());
140                    
141                    PropertyDescriptor propertyDescriptor = beanDescriptor.getConstraintsForProperty(currentProperty);
142                    
143                    if(propertyDescriptor == null) return;
144                    
145                    final Set<ConstraintViolation<Object>> violations = validator.validateValue(
146                                                    (Class<Object>) beanValidationContext.getBeanType(), currentProperty, 
147                                                    value, beanValidationGroupSource.get());
148                    
149                    if (violations.isEmpty()) 
150                    {
151                            return;
152                    }
153                    
154                    final StringBuilder builder = new StringBuilder();
155                    
156                    for (Iterator iterator = violations.iterator(); iterator.hasNext();) 
157                    {
158                            ConstraintViolation<?> violation = (ConstraintViolation<Object>) iterator.next();
159                            
160                            builder.append(format("%s %s", field.getLabel(), violation.getMessage()));
161                            
162                            if(iterator.hasNext())
163                                    builder.append(", ");
164            
165                    }
166                    
167                    throw new ValidationException(builder.toString());
168    
169            }
170            
171            private String interpolateMessage(final ConstraintDescriptor<?> descriptor)
172            {
173                    String messageTemplate = (String) descriptor.getAttributes().get("message");
174                    
175                    MessageInterpolator messageInterpolator = validatorFactory.getMessageInterpolator();
176                    
177                    return messageInterpolator.interpolate(messageTemplate, new Context() 
178                    {
179    
180                public ConstraintDescriptor<?> getConstraintDescriptor() 
181                {
182                  return descriptor;
183                }
184    
185                public Object getValidatedValue() 
186                {
187                  return null;
188                }
189            });
190            }
191    }