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