All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.wicket.bean.validation.PropertyValidator Maven / Gradle / Ivy

There is a newer version: 10.1.0
Show newest version
package org.apache.wicket.bean.validation;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import javax.validation.ConstraintViolation;
import javax.validation.Validator;
import javax.validation.constraints.NotNull;
import javax.validation.groups.Default;
import javax.validation.metadata.ConstraintDescriptor;

import org.apache.wicket.Component;
import org.apache.wicket.behavior.Behavior;
import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.markup.html.form.FormComponent;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.PropertyModel;
import org.apache.wicket.validation.IValidatable;
import org.apache.wicket.validation.IValidator;

/**
 * Validator that delegates to the bean validation framework. The integration has to be first
 * configured using {@link BeanValidationConfiguration}.
 * 
 * 

* The validator must be provided a {@link Property}, unless one can be resolved from the component * implicitly. By default the configuration contains the {@link DefaultPropertyResolver} so * {@link PropertyModel}s are supported out of the box - when attached to a component with a * property model the property does not need to be specified explicitly. *

* *

* The validator will set the required flag on the form component it is attached to based on the * presence of the @NotNull annotation. Notice, the required flag will only be set to {@code true}, * components with the required flag already set to {@code true} will not have the flag set to * {@code false} by this validator. *

* *

* The validator will allow {@link ITagModifier}s configured in {@link BeanValidationConfiguration} * to mutate the markup tag of the component it is attached to. *

* *

* The validator specifies default error messages in the {@code PropertyValidator.properties} file. * These values can be overridden in the application subclass' property files globally or in the * page or panel properties locally. See this file for the default messages supported. *

* * @author igor * * @param */ public class PropertyValidator extends Behavior implements IValidator { private static final Class[] EMPTY = new Class[0]; private FormComponent component; // the trailing underscore means that these members should not be used // directly. ALWAYS use the respective getter instead. private Property property_; private final IModel[]> groups_; /** * A flag indicating whether the component has been configured at least once. */ private boolean requiredFlagSet; public PropertyValidator(Class... groups) { this(null, groups); } public PropertyValidator(IModel[]> groups) { this(null, groups); } public PropertyValidator(Property property, Class... groups) { this(property, new GroupsModel(groups)); } public PropertyValidator(Property property, IModel[]> groups) { this.property_ = property; this.groups_ = groups; } /** * To support debugging, trying to provide useful information where possible * @return */ private String createUnresolvablePropertyMessage(FormComponent component) { String baseMessage = "Could not resolve Bean Property from component: " + component + ". (Hints:) Possible causes are a typo in the PropertyExpression, a null reference or a model that does not work in combination with a " + IPropertyResolver.class.getSimpleName() + "."; IModel model = ValidationModelResolver.resolvePropertyModelFrom(component); if (model != null) { baseMessage += " Model : " + model; } return baseMessage; } private Property getProperty() { if (property_ == null) { BeanValidationContext config = BeanValidationConfiguration.get(); property_ = config.resolveProperty(component); if (property_ == null) { throw new IllegalStateException(createUnresolvablePropertyMessage(component)); } } return property_; } private Class[] getGroups() { if (groups_ == null) { return EMPTY; } return groups_.getObject(); } @SuppressWarnings("unchecked") @Override public void bind(Component component) { if (this.component != null) { throw new IllegalStateException( // "This validator has already been added to component: " + this.component + ". This validator does not support reusing instances, please create a new one"); } if (!(component instanceof FormComponent)) { throw new IllegalStateException(getClass().getSimpleName() + " can only be added to FormComponents"); } // TODO add a validation key that appends the type so we can have // different messages for // @Size on String vs Collection - done but need to add a key for each // superclass/interface this.component = (FormComponent)component; } @Override public void onConfigure(Component component) { super.onConfigure(component); if (requiredFlagSet == false) { // "Required" flag is calculated upon component's model property, so // we must ensure, // that model object is accessible (i.e. component is already added // in a page). requiredFlagSet = true; if (isRequired()) { this.component.setRequired(true); } } } @Override public void detach(Component component) { super.detach(component); if (groups_ != null) { groups_.detach(); } } private List findNotNullConstraints() { BeanValidationContext config = BeanValidationConfiguration.get(); Validator validator = config.getValidator(); Property property = getProperty(); List constraints = new ArrayList(); Iterator> it = new ConstraintIterator(validator, property); while (it.hasNext()) { ConstraintDescriptor desc = it.next(); if (desc.getAnnotation().annotationType().equals(NotNull.class)) { constraints.add((NotNull)desc.getAnnotation()); } } return constraints; } boolean isRequired() { List constraints = findNotNullConstraints(); if (constraints.isEmpty()) { return false; } HashSet> validatorGroups = new HashSet>(); validatorGroups.addAll(Arrays.asList(getGroups())); for (NotNull constraint : constraints) { if (canApplyToDefaultGroup(constraint) && validatorGroups.isEmpty()) { return true; } for (Class constraintGroup : constraint.groups()) { if (validatorGroups.contains(constraintGroup)) { return true; } } } return false; } private boolean canApplyToDefaultGroup(NotNull constraint) { List> groups = Arrays.asList(constraint.groups()); //the constraint can be applied to default group either if its group array is empty //or if it contains javax.validation.groups.Default return groups.size() == 0 || groups.contains(Default.class); } @Override @SuppressWarnings({ "rawtypes", "unchecked" }) public void onComponentTag(Component component, ComponentTag tag) { super.onComponentTag(component, tag); BeanValidationContext config = BeanValidationConfiguration.get(); Validator validator = config.getValidator(); Property property = getProperty(); // find any tag modifiers that apply to the constraints of the property // being validated // and allow them to modify the component tag Iterator> it = new ConstraintIterator(validator, property, getGroups()); while (it.hasNext()) { ConstraintDescriptor desc = it.next(); ITagModifier modifier = config.getTagModifier(desc.getAnnotation().annotationType()); if (modifier != null) { modifier.modify((FormComponent)component, tag, desc.getAnnotation()); } } } @SuppressWarnings("unchecked") @Override public void validate(IValidatable validatable) { BeanValidationContext config = BeanValidationConfiguration.get(); Validator validator = config.getValidator(); Property property = getProperty(); // validate the value using the bean validator Set violations = validator.validateValue(property.getOwner(), property.getName(), validatable.getValue(), getGroups()); // iterate over violations and report them for (ConstraintViolation violation : (Set>)violations) { validatable.error(config.getViolationTranslator().convert(violation)); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy