001 // Copyright 2010, 2012 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 org.apache.tapestry5.Field;
017 import org.apache.tapestry5.FieldValidator;
018 import org.apache.tapestry5.MarkupWriter;
019 import org.apache.tapestry5.ValidationException;
020 import org.apache.tapestry5.beanvalidator.BeanValidatorGroupSource;
021 import org.apache.tapestry5.beanvalidator.ClientConstraintDescriptor;
022 import org.apache.tapestry5.beanvalidator.ClientConstraintDescriptorSource;
023 import org.apache.tapestry5.internal.BeanValidationContext;
024 import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
025 import org.apache.tapestry5.json.JSONObject;
026 import org.apache.tapestry5.services.Environment;
027 import org.apache.tapestry5.services.FormSupport;
028
029 import javax.validation.ConstraintViolation;
030 import javax.validation.MessageInterpolator;
031 import javax.validation.MessageInterpolator.Context;
032 import javax.validation.Validator;
033 import javax.validation.ValidatorFactory;
034 import javax.validation.metadata.BeanDescriptor;
035 import javax.validation.metadata.ConstraintDescriptor;
036 import javax.validation.metadata.PropertyDescriptor;
037
038 import java.lang.annotation.Annotation;
039 import java.util.Iterator;
040 import java.util.Map;
041 import java.util.Set;
042
043 import static java.lang.String.format;
044
045
046 public class BeanFieldValidator implements FieldValidator
047 {
048 private final Field field;
049 private final ValidatorFactory validatorFactory;
050 private final BeanValidatorGroupSource beanValidationGroupSource;
051 private final ClientConstraintDescriptorSource clientValidatorSource;
052 private final FormSupport formSupport;
053 private final Environment environment;
054
055 public BeanFieldValidator(Field field,
056 ValidatorFactory validatorFactory,
057 BeanValidatorGroupSource beanValidationGroupSource,
058 ClientConstraintDescriptorSource clientValidatorSource,
059 FormSupport formSupport,
060 Environment environment)
061 {
062 this.field = field;
063 this.validatorFactory = validatorFactory;
064 this.beanValidationGroupSource = beanValidationGroupSource;
065 this.clientValidatorSource = clientValidatorSource;
066 this.formSupport = formSupport;
067 this.environment = environment;
068 }
069
070 public boolean isRequired()
071 {
072 return false;
073 }
074
075 public void render(final MarkupWriter writer)
076 {
077 final BeanValidationContext beanValidationContext = environment.peek(BeanValidationContext.class);
078
079 if (beanValidationContext == null)
080 {
081 return;
082 }
083
084 final Validator validator = validatorFactory.getValidator();
085
086 BeanDescriptor beanDescriptor = validator.getConstraintsForClass(beanValidationContext.getBeanType());
087
088 String currentProperty = beanValidationContext.getCurrentProperty();
089
090 if (currentProperty == null) return;
091
092 PropertyDescriptor propertyDescriptor = beanDescriptor.getConstraintsForProperty(currentProperty);
093
094 if (propertyDescriptor == null) return;
095
096 for (final ConstraintDescriptor<?> descriptor : propertyDescriptor.getConstraintDescriptors())
097 {
098 Class<? extends Annotation> annotationType = descriptor.getAnnotation().annotationType();
099
100 ClientConstraintDescriptor clientConstraintDescriptor = clientValidatorSource.getConstraintDescriptor(annotationType);
101
102 if (clientConstraintDescriptor == null)
103 {
104 continue;
105 }
106
107 String message = format("%s %s", field.getLabel(), interpolateMessage(descriptor));
108 JSONObject specs = new JSONObject();
109
110 Map<String, Object> attributes = CollectionFactory.newMap();
111
112 for (String attribute : clientConstraintDescriptor.getAttributes())
113 {
114 Object object = descriptor.getAttributes().get(attribute);
115
116 if (object == null)
117 {
118 throw new NullPointerException(
119 String.format("Attribute '%s' of %s is null but is required to apply client-side validation.",
120 attribute, descriptor));
121 }
122 attributes.put(attribute, object);
123
124 specs.put(attribute, object);
125
126 }
127
128 formSupport.addValidation(field, clientConstraintDescriptor.getValidatorName(), message, specs);
129
130 }
131
132 }
133
134 @SuppressWarnings("unchecked")
135 public void validate(final Object value) throws ValidationException
136 {
137
138 final BeanValidationContext beanValidationContext = environment.peek(BeanValidationContext.class);
139
140 if (beanValidationContext == null)
141 {
142 return;
143 }
144
145 final Validator validator = validatorFactory.getValidator();
146
147 String currentProperty = beanValidationContext.getCurrentProperty();
148
149 if (currentProperty == null) return;
150
151 Class<?> beanType = beanValidationContext.getBeanType();
152 String[] path = currentProperty.split("\\.");
153 BeanDescriptor beanDescriptor = validator.getConstraintsForClass(beanType);
154
155 for (int i = 1; i < path.length - 1; i++)
156 {
157 Class<?> constrainedPropertyClass = getConstrainedPropertyClass(beanDescriptor, path[i]);
158 if (constrainedPropertyClass != null) {
159 beanType = constrainedPropertyClass;
160 beanDescriptor = validator.getConstraintsForClass(beanType);
161 }
162 }
163
164 final String propertyName = path[path.length - 1];
165 PropertyDescriptor propertyDescriptor = beanDescriptor.getConstraintsForProperty(propertyName);
166
167 if (propertyDescriptor == null) return;
168
169 final Set<ConstraintViolation<Object>> violations = validator.validateValue(
170 (Class<Object>) beanType, propertyName,
171 value, beanValidationGroupSource.get());
172
173 if (violations.isEmpty())
174 {
175 return;
176 }
177
178 final StringBuilder builder = new StringBuilder();
179
180 for (Iterator<ConstraintViolation<Object>> iterator = violations.iterator(); iterator.hasNext(); )
181 {
182 ConstraintViolation<?> violation = iterator.next();
183
184 builder.append(format("%s %s", field.getLabel(), violation.getMessage()));
185
186 if (iterator.hasNext())
187 builder.append(", ");
188
189 }
190
191 throw new ValidationException(builder.toString());
192
193 }
194
195 /**
196 * Returns the class of a given property, but only if it is a constrained property of the
197 * parent class. Otherwise, it returns null.
198 */
199 final private static Class<?> getConstrainedPropertyClass(BeanDescriptor beanDescriptor, String propertyName)
200 {
201 Class<?> clasz = null;
202 for (PropertyDescriptor descriptor : beanDescriptor.getConstrainedProperties())
203 {
204 if (descriptor.getPropertyName().equals(propertyName))
205 {
206 clasz = descriptor.getElementClass();
207 break;
208 }
209 }
210 return clasz;
211 }
212
213 private String interpolateMessage(final ConstraintDescriptor<?> descriptor)
214 {
215 String messageTemplate = (String) descriptor.getAttributes().get("message");
216
217 MessageInterpolator messageInterpolator = validatorFactory.getMessageInterpolator();
218
219 return messageInterpolator.interpolate(messageTemplate, new Context()
220 {
221
222 public ConstraintDescriptor<?> getConstraintDescriptor()
223 {
224 return descriptor;
225 }
226
227 public Object getValidatedValue()
228 {
229 return null;
230 }
231 });
232 }
233 }