/* * 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.PathImpl; import org.apache.bval.model.ValidationContext; import org.apache.bval.model.ValidationListener; import javax.validation.ConstraintViolation; import javax.validation.MessageInterpolator; import javax.validation.Path; import javax.validation.metadata.ConstraintDescriptor; import java.lang.annotation.ElementType; import java.util.HashSet; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; /** * Description: JSR-303 {@link ValidationListener} implementation; provides {@link ConstraintViolation}s.
*/ public final class ConstraintValidationListener implements ValidationListener { private final Set> constraintViolations = new HashSet>(); private final T rootBean; private final Class rootBeanType; // TODO: Currently there is no need for atomicity here as all the validation process // is single-threaded and it's unlikely to change in the near future. private final AtomicInteger compositeDepth = new AtomicInteger(0); private boolean hasCompositeError; /** * Create a new ConstraintValidationListener instance. * @param aRootBean * @param rootBeanType */ public ConstraintValidationListener(T aRootBean, Class rootBeanType) { this.rootBean = aRootBean; this.rootBeanType = rootBeanType; } /** * {@inheritDoc} */ public void addError(String reason, ValidationContext context) { addError(reason, null, context); } /** * {@inheritDoc} */ public void addError(Error error, ValidationContext context) { if (error.getOwner() instanceof Path) { addError(error.getReason(), (Path) error.getOwner(), context); } else { addError(error.getReason(), null, context); } } private void addError(String messageTemplate, Path propPath, ValidationContext context) { if (compositeDepth.get() > 0) { hasCompositeError |= true; return; } final Object value; final ConstraintDescriptor descriptor; final String message; if (context instanceof GroupValidationContext) { GroupValidationContext gcontext = (GroupValidationContext) context; value = gcontext.getValidatedValue(); if (gcontext instanceof MessageInterpolator.Context) { message = gcontext.getMessageResolver() .interpolate(messageTemplate, (MessageInterpolator.Context) gcontext); } else { message = gcontext.getMessageResolver().interpolate(messageTemplate, null); } descriptor = gcontext.getConstraintValidation().asSerializableDescriptor(); if (propPath == null) propPath = gcontext.getPropertyPath(); } else { if (context.getMetaProperty() == null) value = context.getBean(); else value = context.getPropertyValue(); message = messageTemplate; if (propPath == null) propPath = PathImpl.createPathFromString(context.getPropertyName()); descriptor = null; } ElementType elementType = (context.getAccess() != null) ? context.getAccess().getElementType() : null; ConstraintViolationImpl ic = new ConstraintViolationImpl(messageTemplate, message, rootBean, context.getBean(), propPath, value, descriptor, rootBeanType, elementType); constraintViolations.add(ic); } /** * Get the {@link ConstraintViolation}s accumulated by this {@link ConstraintValidationListener}. * @return {@link Set} of {@link ConstraintViolation} */ public Set> getConstraintViolations() { return constraintViolations; } /** * Learn whether no violations were found. * @return boolean */ public boolean isEmpty() { return constraintViolations.isEmpty(); } /** * Get the root bean. * @return T */ public T getRootBean() { return rootBean; } /** * Get the root bean type of this {@link ConstraintValidationListener}. * @return Class */ public Class getRootBeanType() { return rootBeanType; } /** * Get the count of encountered violations. * @return int */ public int violationsSize() { return constraintViolations.size(); } /** * Learn whether there are violations available. * If in report-as-single-violation mode, the result is scoped accordingly. * Note that this means you must check before exiting report-as-single-violation mode * @return boolean */ public boolean hasViolations() { return compositeDepth.get() == 0 ? !constraintViolations.isEmpty() : hasCompositeError; } /** * Signify the beginning of a report-as-single-violation composite validation. * @return true as this call caused the listener to enter report-as-single-violation mode */ public boolean beginReportAsSingle() { return compositeDepth.incrementAndGet() == 1; } /** * Signify the end of a report-as-single-violation composite validation. * @return true as this call caused the listener to exit report-as-single-violation mode */ public boolean endReportAsSingle() { return compositeDepth.decrementAndGet() == 0; } }