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 public boolean isRequired() 070 { 071 return false; 072 } 073 074 public void render(final MarkupWriter writer) 075 { 076 final BeanValidationContext beanValidationContext = environment.peek(BeanValidationContext.class); 077 078 if (beanValidationContext == null) 079 { 080 return; 081 } 082 083 final Validator validator = validatorFactory.getValidator(); 084 085 BeanDescriptor beanDescriptor = validator.getConstraintsForClass(beanValidationContext.getBeanType()); 086 087 String currentProperty = beanValidationContext.getCurrentProperty(); 088 089 if (currentProperty == null) return; 090 091 PropertyDescriptor propertyDescriptor = beanDescriptor.getConstraintsForProperty(currentProperty); 092 093 if (propertyDescriptor == null) return; 094 095 for (final ConstraintDescriptor<?> descriptor : propertyDescriptor.getConstraintDescriptors()) 096 { 097 Class<? extends Annotation> annotationType = descriptor.getAnnotation().annotationType(); 098 099 ClientConstraintDescriptor clientConstraintDescriptor = clientValidatorSource.getConstraintDescriptor(annotationType); 100 101 if (clientConstraintDescriptor == null) 102 { 103 continue; 104 } 105 106 String message = format("%s %s", field.getLabel(), interpolateMessage(descriptor)); 107 108 Map<String, Object> attributes = CollectionFactory.newMap(); 109 110 for (String attribute : clientConstraintDescriptor.getAttributes()) 111 { 112 Object object = descriptor.getAttributes().get(attribute); 113 114 if (object == null) 115 { 116 throw new NullPointerException( 117 String.format("Attribute '%s' of %s is null but is required to apply client-side validation.", 118 attribute, descriptor)); 119 } 120 attributes.put(attribute, object); 121 } 122 123 clientConstraintDescriptor.applyClientValidation(writer, message, attributes); 124 } 125 } 126 127 @SuppressWarnings("unchecked") 128 public void validate(final Object value) throws ValidationException 129 { 130 131 final BeanValidationContext beanValidationContext = environment.peek(BeanValidationContext.class); 132 133 if (beanValidationContext == null) 134 { 135 return; 136 } 137 138 final Validator validator = validatorFactory.getValidator(); 139 140 String currentProperty = beanValidationContext.getCurrentProperty(); 141 142 if (currentProperty == null) return; 143 144 Class<?> beanType = beanValidationContext.getBeanType(); 145 String[] path = currentProperty.split("\\."); 146 BeanDescriptor beanDescriptor = validator.getConstraintsForClass(beanType); 147 148 for (int i = 1; i < path.length - 1; i++) 149 { 150 Class<?> constrainedPropertyClass = getConstrainedPropertyClass(beanDescriptor, path[i]); 151 if (constrainedPropertyClass != null) { 152 beanType = constrainedPropertyClass; 153 beanDescriptor = validator.getConstraintsForClass(beanType); 154 } 155 } 156 157 final String propertyName = path[path.length - 1]; 158 PropertyDescriptor propertyDescriptor = beanDescriptor.getConstraintsForProperty(propertyName); 159 160 if (propertyDescriptor == null) return; 161 162 final Set<ConstraintViolation<Object>> violations = validator.validateValue( 163 (Class<Object>) beanType, propertyName, 164 value, beanValidationGroupSource.get()); 165 166 if (violations.isEmpty()) 167 { 168 return; 169 } 170 171 final StringBuilder builder = new StringBuilder(); 172 173 for (Iterator<ConstraintViolation<Object>> iterator = violations.iterator(); iterator.hasNext(); ) 174 { 175 ConstraintViolation<?> violation = iterator.next(); 176 177 builder.append(format("%s %s", field.getLabel(), violation.getMessage())); 178 179 if (iterator.hasNext()) 180 builder.append(", "); 181 182 } 183 184 throw new ValidationException(builder.toString()); 185 186 } 187 188 /** 189 * Returns the class of a given property, but only if it is a constrained property of the 190 * parent class. Otherwise, it returns null. 191 */ 192 final private static Class<?> getConstrainedPropertyClass(BeanDescriptor beanDescriptor, String propertyName) 193 { 194 Class<?> clasz = null; 195 for (PropertyDescriptor descriptor : beanDescriptor.getConstrainedProperties()) 196 { 197 if (descriptor.getPropertyName().equals(propertyName)) 198 { 199 clasz = descriptor.getElementClass(); 200 break; 201 } 202 } 203 return clasz; 204 } 205 206 private String interpolateMessage(final ConstraintDescriptor<?> descriptor) 207 { 208 String messageTemplate = (String) descriptor.getAttributes().get("message"); 209 210 MessageInterpolator messageInterpolator = validatorFactory.getMessageInterpolator(); 211 212 return messageInterpolator.interpolate(messageTemplate, new Context() 213 { 214 215 public ConstraintDescriptor<?> getConstraintDescriptor() 216 { 217 return descriptor; 218 } 219 220 public Object getValidatedValue() 221 { 222 return null; 223 } 224 }); 225 } 226}