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.
014package org.apache.tapestry5.internal.beanvalidator;
015
016import static java.lang.String.format;
017
018import java.lang.annotation.Annotation;
019import java.util.Iterator;
020import java.util.Set;
021
022import javax.validation.ConstraintViolation;
023import javax.validation.MessageInterpolator;
024import javax.validation.Validator;
025import javax.validation.ValidatorFactory;
026import javax.validation.MessageInterpolator.Context;
027import javax.validation.metadata.BeanDescriptor;
028import javax.validation.metadata.ConstraintDescriptor;
029import javax.validation.metadata.PropertyDescriptor;
030
031import org.apache.tapestry5.Field;
032import org.apache.tapestry5.FieldValidator;
033import org.apache.tapestry5.MarkupWriter;
034import org.apache.tapestry5.ValidationException;
035import org.apache.tapestry5.beanvalidator.BeanValidatorGroupSource;
036import org.apache.tapestry5.beanvalidator.ClientConstraintDescriptor;
037import org.apache.tapestry5.beanvalidator.ClientConstraintDescriptorSource;
038import org.apache.tapestry5.internal.BeanValidationContext;
039import org.apache.tapestry5.json.JSONObject;
040import org.apache.tapestry5.services.Environment;
041import org.apache.tapestry5.services.FormSupport;
042
043
044public 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}