/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.bval.jsr303; import org.apache.bval.jsr303.util.NodeImpl; import org.apache.bval.jsr303.util.PathImpl; import org.apache.bval.model.Validation; import org.apache.bval.model.ValidationContext; import org.apache.bval.model.ValidationListener; import org.apache.bval.util.AccessStrategy; import javax.validation.ConstraintDefinitionException; import javax.validation.ConstraintValidator; import javax.validation.Payload; import javax.validation.ValidationException; import javax.validation.metadata.ConstraintDescriptor; import java.io.Serializable; import java.lang.annotation.Annotation; import java.util.*; /** * Description: Adapter between Constraint (JSR303) and Validation (Core)
* this instance is immutable!
*/ public class ConstraintValidation implements Validation, ConstraintDescriptor { private static final String ANNOTATION_MESSAGE = "message"; private final ConstraintValidator validator; private T annotation; // for metadata request API private final AccessStrategy access; private final boolean reportFromComposite; private final Map attributes; private Set> composedConstraints; /** * the owner is the type where the validation comes from. * it is used to support implicit grouping. */ private final Class owner; private Set> groups; private Set> payload; private Class>[] validatorClasses; /** * Create a new ConstraintValidation instance. * @param validatorClasses * @param validator - the constraint validator * @param annotation - the annotation of the constraint * @param owner - the type where the annotated element is placed * (class, interface, annotation type) * @param access - how to access the value * @param reportFromComposite */ public ConstraintValidation( Class>[] validatorClasses, ConstraintValidator validator, T annotation, Class owner, AccessStrategy access, boolean reportFromComposite) { this.attributes = new HashMap(); this.validatorClasses = validatorClasses; this.validator = validator; this.annotation = annotation; this.owner = owner; this.access = access; this.reportFromComposite = reportFromComposite; } /** * Return a {@link Serializable} {@link ConstraintDescriptor} capturing a snapshot of current state. * @return {@link ConstraintDescriptor} */ public ConstraintDescriptor asSerializableDescriptor() { return new ConstraintDescriptorImpl(this); } /** * Set the applicable validation groups. * @param groups */ void setGroups(Set> groups) { this.groups = groups; this.attributes.put("groups", groups.toArray(new Class[groups.size()])); } /** * Set the payload. * @param payload */ void setPayload(Set> payload) { this.payload = payload; this.attributes.put("payload", payload.toArray(new Class[payload.size()])); } /** * {@inheritDoc} */ public boolean isReportAsSingleViolation() { return reportFromComposite; } /** * Add a composing constraint. * @param aConstraintValidation to add */ public void addComposed(ConstraintValidation aConstraintValidation) { if (composedConstraints == null) { composedConstraints = new HashSet>(); } composedConstraints.add(aConstraintValidation); } /** * {@inheritDoc} */ public void validate(ValidationContext context) { validate((GroupValidationContext) context); } /** * Validate a {@link GroupValidationContext}. * @param context root */ public void validate(GroupValidationContext context) { context.setConstraintValidation(this); /** * execute unless the given validation constraint has already been processed * during this validation routine (as part of a previous group match) */ if (!isMemberOf(context.getCurrentGroup().getGroup())) { return; // do not validate in the current group } if (context.getCurrentOwner() != null && this.owner != context.getCurrentOwner()) { return; } if (validator != null && !context.collectValidated(validator)) return; // already done if (context.getMetaProperty() != null && !isReachable(context)) { return; } // process composed constraints if (isReportAsSingleViolation()) { ConstraintValidationListener listener = context.getListener(); listener.beginReportAsSingle(); boolean failed = false; try { // stop validating when already failed and ReportAsSingleInvalidConstraint = true ? for (Iterator> composed = getComposingValidations().iterator(); !failed && composed.hasNext();) { composed.next().validate(context); failed = listener.hasViolations(); } } finally { listener.endReportAsSingle(); // Restore current constraint validation context.setConstraintValidation(this); } if (failed) { // TODO RSt - how should the composed constraint error report look like? ConstraintValidatorContextImpl jsrContext = new ConstraintValidatorContextImpl(context, this); addErrors(context, jsrContext); // add defaultErrorMessage only*/ return; } } else { for (ConstraintValidation composed : getComposingValidations()) { composed.validate(context); } // Restore current constraint validation context.setConstraintValidation(this); } if (validator != null) { ConstraintValidatorContextImpl jsrContext = new ConstraintValidatorContextImpl(context, this); @SuppressWarnings("unchecked") final ConstraintValidator objectValidator = (ConstraintValidator) validator; if (!objectValidator.isValid(context.getValidatedValue(), jsrContext)) { addErrors(context, jsrContext); } } } /** * Initialize the validator (if not null) with the stored * annotation. */ public void initialize() { if (null != validator) { try { validator.initialize(annotation); } catch (RuntimeException e) { // Either a "legit" problem initializing the validator or a // ClassCastException if the validator associated annotation is // not a supertype of the validated annotation. throw new ConstraintDefinitionException( "Incorrect validator [" + validator.getClass().getCanonicalName() + "] for annotation " + annotation.annotationType().getCanonicalName(), e); } } } private boolean isReachable(GroupValidationContext context) { PathImpl path = context.getPropertyPath(); NodeImpl node = path.getLeafNode(); PathImpl beanPath = path.getPathWithoutLeafNode(); if (beanPath == null) { beanPath = PathImpl.create(null); } try { if (!context.getTraversableResolver() .isReachable(context.getBean(), node, context.getRootMetaBean().getBeanClass(), beanPath, access.getElementType())) return false; } catch (RuntimeException e) { throw new ValidationException( "Error in TraversableResolver.isReachable() for " + context.getBean(), e); } return true; } private void addErrors(GroupValidationContext context, ConstraintValidatorContextImpl jsrContext) { for (ValidationListener.Error each : jsrContext.getErrorMessages()) { context.getListener().addError(each, context); } } /** * {@inheritDoc} */ public String toString() { return "ConstraintValidation{" + validator + '}'; } /** * Get the message template used by this constraint. * @return String */ public String getMessageTemplate() { return (String) attributes.get(ANNOTATION_MESSAGE); } /** * Get the {@link ConstraintValidator} invoked by this {@link ConstraintValidation}. * @return */ public ConstraintValidator getValidator() { return validator; } /** * Learn whether this {@link ConstraintValidation} belongs to the specified group. * @param reqGroup * @return boolean */ protected boolean isMemberOf(Class reqGroup) { return groups.contains(reqGroup); } /** * Get the owning class of this {@link ConstraintValidation}. * @return Class */ public Class getOwner() { return owner; } /** * {@inheritDoc} */ public T getAnnotation() { return annotation; } /** * Get the {@link AccessStrategy} used by this {@link ConstraintValidation}. * @return {@link AccessStrategy} */ public AccessStrategy getAccess() { return access; } /** * Override the Annotation set at construction. * @param annotation */ public void setAnnotation(T annotation) { this.annotation = annotation; } /////////////////////////// ConstraintDescriptor implementation /** * {@inheritDoc} */ public Map getAttributes() { return attributes; } /** * {@inheritDoc} */ @SuppressWarnings("unchecked") public Set> getComposingConstraints() { return composedConstraints == null ? Collections.EMPTY_SET : composedConstraints; } /** * Get the composing {@link ConstraintValidation} objects. This is effectively * an implementation-specific analogue to {@link #getComposingConstraints()}. * @return {@link Set} of {@link ConstraintValidation} */ @SuppressWarnings("unchecked") Set> getComposingValidations() { return composedConstraints == null ? Collections.EMPTY_SET : composedConstraints; } /** * {@inheritDoc} */ public Set> getGroups() { return groups; } /** * {@inheritDoc} */ public Set> getPayload() { return payload; } /** * {@inheritDoc} */ public List>> getConstraintValidatorClasses() { if (validatorClasses == null) { return Collections.emptyList(); } return Arrays.asList(validatorClasses); } }