001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.wicket.bean.validation; 018 019import java.lang.annotation.Annotation; 020import java.util.ArrayList; 021import java.util.Collections; 022import java.util.List; 023import java.util.Map; 024import java.util.concurrent.ConcurrentHashMap; 025import java.util.concurrent.CopyOnWriteArrayList; 026import java.util.function.Supplier; 027 028import javax.validation.Validator; 029import javax.validation.constraints.NotEmpty; 030import javax.validation.constraints.NotNull; 031import javax.validation.constraints.Size; 032import javax.validation.metadata.ConstraintDescriptor; 033 034import org.apache.wicket.Application; 035import org.apache.wicket.MetaDataKey; 036import org.apache.wicket.markup.html.form.FormComponent; 037import org.apache.wicket.util.lang.Args; 038 039/** 040 * Configures bean validation and integrates it with Wicket 041 * 042 * @author igor 043 * 044 */ 045public class BeanValidationConfiguration implements BeanValidationContext 046{ 047 private static final MetaDataKey<BeanValidationConfiguration> KEY = new MetaDataKey<>() 048 { 049 }; 050 051 /** 052 * Default list of annotations that make a component required. 053 */ 054 static final List<Class<? extends Annotation>> REQUIRED_ANNOTATIONS; 055 static 056 { 057 List<Class<? extends Annotation>> tmp = new ArrayList<>(); 058 tmp.add(NotNull.class); 059 try 060 { 061 tmp.add(Class.forName("javax.validation.constraints.NotBlank") 062 .asSubclass(Annotation.class)); 063 tmp.add(Class.forName("javax.validation.constraints.NotEmpty") 064 .asSubclass(Annotation.class)); 065 } 066 catch (ClassNotFoundException e) 067 { 068 // ignore exception, we are using bean validation 1.1 069 } 070 REQUIRED_ANNOTATIONS = Collections.unmodifiableList(tmp); 071 } 072 073 private Supplier<Validator> validatorProvider = new DefaultValidatorProvider(); 074 075 private IViolationTranslator violationTranslator = new DefaultViolationTranslator(); 076 077 private List<IPropertyResolver> propertyResolvers = new CopyOnWriteArrayList<>(); 078 079 private Map<Class<?>, ITagModifier<? extends Annotation>> tagModifiers = new ConcurrentHashMap<>(); 080 081 public BeanValidationConfiguration() 082 { 083 add(new DefaultPropertyResolver()); 084 register(Size.class, new SizeTagModifier()); 085 } 086 087 /** 088 * Registers a tag modifier for a specific constraint annotation. 089 * <p> 090 * By default {@link Size} constraints are automatically mapped to <code>maxlength</code> of text inputs, 091 * this can be disabled by registering a {@link ITagModifier#NO_OP} instead: 092 * <code> 093 * configuration.register(Size.class, ITagModifier.NO_OP}); 094 * </code> 095 * 096 * @param annotationType 097 * constraint annotation such as {@link Size} 098 * @param modifier 099 * tag modifier to use 100 * @return {@code this} 101 */ 102 public <T extends Annotation> BeanValidationConfiguration register(Class<T> annotationType, 103 ITagModifier<T> modifier) 104 { 105 Args.notNull(annotationType, "annotationType"); 106 Args.notNull(modifier, "modifier"); 107 108 tagModifiers.put(annotationType, modifier); 109 110 return this; 111 } 112 113 /** 114 * Get the registered modifier for the given annotation. 115 * 116 * @see #register(Class, ITagModifier) 117 */ 118 @Override 119 @SuppressWarnings("unchecked") 120 public <T extends Annotation> ITagModifier<T> getTagModifier(Class<T> annotationType) 121 { 122 Args.notNull(annotationType, "annotationType"); 123 124 return (ITagModifier<T>)tagModifiers.get(annotationType); 125 } 126 127 /** 128 * Adds a property resolver to the configuration. Property resolvers registered last are the 129 * first to be allowed to resolve the property. 130 * 131 * @param resolver 132 * @return {@code this} 133 */ 134 public BeanValidationConfiguration add(IPropertyResolver resolver) 135 { 136 Args.notNull(resolver, "resolver"); 137 138 // put newly added resolvers in the beginning so its possible to override previously added 139 // resolvers with newer ones 140 propertyResolvers.add(0, resolver); 141 142 return this; 143 } 144 145 /** 146 * @return validator 147 */ 148 @Override 149 public Validator getValidator() 150 { 151 return validatorProvider.get(); 152 } 153 154 /** 155 * Sets the provider used to retrieve {@link Validator} instances 156 * 157 * @param validatorProvider 158 */ 159 public void setValidatorProvider(Supplier<Validator> validatorProvider) 160 { 161 Args.notNull(validatorProvider, "validatorProvider"); 162 163 this.validatorProvider = validatorProvider; 164 } 165 166 /** 167 * Binds this configuration to the application instance 168 * 169 * @param application 170 */ 171 public void configure(Application application) 172 { 173 application.setMetaData(KEY, this); 174 } 175 176 /** @return registered violation translator */ 177 @Override 178 public IViolationTranslator getViolationTranslator() 179 { 180 return violationTranslator; 181 } 182 183 /** 184 * Registers a violation translator 185 * 186 * @param violationTranslator 187 * A violation translator that will convert {@link javax.validation.ConstraintViolation}s into Wicket's 188 * {@link org.apache.wicket.validation.ValidationError}s 189 */ 190 public void setViolationTranslator(IViolationTranslator violationTranslator) 191 { 192 Args.notNull(violationTranslator, "violationTranslator"); 193 194 this.violationTranslator = violationTranslator; 195 } 196 197 /** 198 * Retrieves the validation context (read only version of the configuration). This is how 199 * components retrieve the configuration. 200 * 201 * @return validation context 202 */ 203 public static BeanValidationContext get() 204 { 205 BeanValidationConfiguration config = Application.get().getMetaData(KEY); 206 if (config == null) 207 { 208 throw new IllegalStateException( 209 "Application instance has not yet been configured for bean validation. See BeanValidationConfiguration#configure(Application)"); 210 } 211 return config; 212 } 213 214 @Override 215 public Property resolveProperty(FormComponent<?> component) 216 { 217 for (IPropertyResolver resolver : propertyResolvers) 218 { 219 Property property = resolver.resolveProperty(component); 220 if (property != null) 221 { 222 return property; 223 } 224 } 225 return null; 226 } 227 228 /** 229 * By default {@link NotNull} and {@link NotEmpty} constraints make a component required. 230 * 231 * @param constraint 232 * constraint 233 */ 234 @Override 235 public boolean isRequiredConstraint(ConstraintDescriptor<?> constraint) 236 { 237 return REQUIRED_ANNOTATIONS.contains(constraint.getAnnotation().annotationType()); 238 } 239}