org.eclipse.persistence.jaxb.JAXBBeanValidator Maven / Gradle / Ivy
Show all versions of eclipselink Show documentation
/*
* Copyright (c) 2015, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// Contributors:
// Marcel Valovy - 2.6 - initial implementation
package org.eclipse.persistence.jaxb;
import java.security.CodeSource;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Set;
import java.util.concurrent.locks.ReentrantLock;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import jakarta.validation.Path;
import jakarta.validation.Validation;
import jakarta.validation.ValidationException;
import jakarta.validation.Validator;
import jakarta.validation.ValidatorFactory;
import jakarta.validation.groups.Default;
import org.eclipse.persistence.exceptions.BeanValidationException;
import org.eclipse.persistence.internal.security.PrivilegedAccessHelper;
import org.eclipse.persistence.jaxb.xmlmodel.XmlBindings;
import org.eclipse.persistence.logging.DefaultSessionLog;
import org.eclipse.persistence.logging.SessionLog;
/**
* INTERNAL:
*
* JAXB Bean Validator. Serves three purposes:
* 1. Determines if the validation callback should take place on the (un)marshal call.
* 2. Processes the validation.
* 3. Stores the constraintViolations from the last validation call.
*
* @author Marcel Valovy - [email protected]
* @since 2.6
*/
class JAXBBeanValidator {
/**
* Represents the Default validation group. Storing it in constant saves resources.
*/
static final Class>[] DEFAULT_GROUP_ARRAY = new Class>[] { Default.class };
/**
* Represents the difference between words 'marshalling' and 'unmarshalling';
*/
private static final String PREFIX_UNMARSHALLING = "un";
/**
* Prevents endless invocation loops between unmarshaller - validator - unmarshaller.
* Only used / needed in case {@link #noOptimisation} is {@code true}.
*/
private static final ReentrantLock lock = new ReentrantLock();
/**
* Disable optimisations that skip bean validation processes on non-constrained objects.
*/
private boolean noOptimisation = false;
/**
* Stores {@link #PREFIX_UNMARSHALLING} if this instance belongs to
* {@link org.eclipse.persistence.jaxb.JAXBUnmarshaller}, otherwise stores empty String.
*/
private final String prefix;
/**
* Reference to {@link org.eclipse.persistence.jaxb.JAXBContext}. Allows for callbacks.
*/
private final JAXBContext context;
/**
* Stores the {@link jakarta.validation.Validator} implementation. Once found, the reference is preserved.
*/
private Validator validator;
/**
* Stores constraint violations returned by last call to {@link jakarta.validation.Validator#validate(Object, Class[])}.
* After each {@link #validate(Object, Class[])} call, the reference is replaced.
*/
private Set> constraintViolations = Collections.emptySet();
/**
* Computed value saying if the validation can proceed under current conditions, represented by:
*
* - {@link #beanValidationMode}
* - {@link jakarta.validation.Validator} implementation present on classpath
*
*
* Value is recomputed only on {@link #changeInternalState()} call.
*/
private boolean canValidate;
/**
* Represents a state where {@link #beanValidationMode} mode is set to
* {@link org.eclipse.persistence.jaxb.BeanValidationMode#AUTO} and BV implementation could not be found.
*/
private boolean stopSearchingForValidator;
/**
* This field will usually be {@code null}. However, user may pass his own instance of
* {@link jakarta.validation.ValidatorFactory} to
* {@link #shouldValidate}() method, and it will be assigned to this field.
*
* If not null, {@link #validator} field will be assigned only by calling method
* {@link jakarta.validation.ValidatorFactory#getValidator()} the instance assigned to this field.
*/
private ValidatorFactory validatorFactory;
/**
* Setting initial value to "NONE" will not trigger internalStateChange() when validation is off and save resources.
*/
private BeanValidationMode beanValidationMode = BeanValidationMode.NONE;
// Local logger instance.
private final SessionLog log = new DefaultSessionLog();
/**
* Private constructor. Only to be called by factory methods.
* @param prefix differentiates between marshaller and unmarshaller during logging
* @param context jaxb context reference
*/
private JAXBBeanValidator(String prefix, JAXBContext context) {
this.prefix = prefix;
this.context = context;
}
/**
* Factory method.
*
* The only difference between this method and {@link #getUnmarshallingBeanValidator} is not having
* {@link #PREFIX_UNMARSHALLING} in String messages constructed for exceptions.
*
* @param context jaxb context reference
* @return
* a new instance of {@link JAXBBeanValidator}.
*/
static JAXBBeanValidator getMarshallingBeanValidator(JAXBContext context){
return new JAXBBeanValidator("", context);
}
/**
* Factory method.
*
* The only difference between this method and {@link #getMarshallingBeanValidator} is having
* {@link #PREFIX_UNMARSHALLING} in String messages constructed for exceptions.
*
* @param context jaxb context reference
* @return
* a new instance of {@link JAXBBeanValidator}.
*/
static JAXBBeanValidator getUnmarshallingBeanValidator(JAXBContext context){
return new JAXBBeanValidator(PREFIX_UNMARSHALLING, context);
}
/**
* PUBLIC:
*
* First, if validation has not been turned off before, check if passed value is constrained.
*
* Second, depending on Bean Validation Mode, either returns false or tries to initialize Validator:
* - AUTO tries to initialize Validator:
* returns true if succeeds, else false.
* - CALLBACK tries to initialize Validator:
* returns true if succeeds, else throws {@link BeanValidationException#providerNotFound}.
* - NONE returns false;
*
* BeanValidationMode is fetched from (un)marshaller upon each call.
* If change in mode is detected, the internal state of the JAXBBeanValidator will be switched.
*
* Third, analyses the value and determines whether validation may be skipped.
*
* @param beanValidationMode Bean validation mode - allowed values AUTO, CALLBACK, NONE.
* @param value validated object. It is passed because validation on some objects may be skipped,
* e.g. non-constrained objects (like XmlBindings).
* @param preferredValidatorFactory Must be {@link ValidatorFactory} or null. Will use this factory as the
* preferred provider; if null, will use javax defaults.
* @param noOptimisation if true, bean validation optimisations that skip non-constrained objects will not be
* performed
* @return
* true if should proceed with validation, else false.
* @throws BeanValidationException
* {@link BeanValidationException#illegalValidationMode} or {@link BeanValidationException#providerNotFound}.
* @since 2.6
*/
boolean shouldValidate (Object value, BeanValidationMode beanValidationMode,
Object preferredValidatorFactory,
boolean noOptimisation) throws BeanValidationException {
if (isValidationEffectivelyOff(beanValidationMode)) return false;
this.noOptimisation = noOptimisation;
if (!isConstrainedObject(value)) return false;
/* Mode or validator factory was changed externally (or it's the first time this method is called). */
if (this.beanValidationMode != beanValidationMode || this.validatorFactory != preferredValidatorFactory) {
this.beanValidationMode = beanValidationMode;
this.validatorFactory = (ValidatorFactory)preferredValidatorFactory;
changeInternalState();
}
/* Is Validation implementation ready to validate. */
return canValidate;
}
/**
* Check if validation is effectively off, i.e. it was previously attempted to turn it on, but that failed.
* @param beanValidationMode user passed beanValidationMode
* @return true if validation is effectively off
*/
private boolean isValidationEffectivelyOff(BeanValidationMode beanValidationMode) {
return ! ((beanValidationMode == BeanValidationMode.AUTO && canValidate) /* most common case */
|| (beanValidationMode == BeanValidationMode.CALLBACK)
/* beanValidationMode is AUTO but canValidate is yet to be resolved */
|| (beanValidationMode != BeanValidationMode.NONE && beanValidationMode != this.beanValidationMode)
);
}
/**
* Check if object contains any bean validation constraints or custom validation constraints.
* @param value object
* @return true if the object is not null and is constrained
*/
private boolean isConstrainedObject(Object value) {
/* Json is allowed to pass a null root object. Avoid NPE & speed things up. */
if (value == null) return false;
if (noOptimisation) {
/* Stops the endless invocation loop which may occur when calling
* Validation#buildDefaultValidatorFactory in a case when the user sets
* custom validation configuration through "validation.xml" file and
* the validation implementation tries to unmarshal the file with MOXy. */
if (lock.isHeldByCurrentThread()) return false;
/* Do not validate XmlBindings. */
return !(value instanceof XmlBindings);
}
/* Ensure that the class contains BV annotations. If not, skip validation & speed things up.
* note: This also effectively skips XmlBindings. */
return context.getBeanValidationHelper().isConstrained(value.getClass());
}
/**
* INTERNAL:
*
* Validates the value, as per BV spec.
* Stores the result of validation in {@link #constraintViolations}.
*
* @param value Object to be validated.
* @param groups Target groups as per BV spec. If null {@link #DEFAULT_GROUP_ARRAY} is used.
*/
void validate(Object value, Class>... groups) throws BeanValidationException {
Class>[] grp = groups;
if (grp == null || grp.length == 0) {
grp = DEFAULT_GROUP_ARRAY;
}
constraintViolations = validator.validate(value, grp);
if (!constraintViolations.isEmpty())
throw buildConstraintViolationException();
}
/**
* @return constraintViolations from the last {@link #validate} call.
*/
Set> getConstraintViolations() {
Set> result = new HashSet<>(constraintViolations.size());
for (ConstraintViolation