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

com.github.ldeitos.validators.AbstractExtendedValidator Maven / Gradle / Ivy

Go to download

Extension for BeanValidation API to provide CDI compatible ConstraintValidatorFactory, easy way to set parameters on message constraint and easy API to add non properties file message sources, like DB, external modules, mainframe routines, etc. This version is Java 17 and JakartaEE 10 compatible.

There is a newer version: 3.0
Show newest version
package com.github.ldeitos.validators;

import static com.github.ldeitos.validation.impl.util.PresentationMessageFormatter.format;
import static java.lang.String.format;

import java.lang.annotation.Annotation;

import javax.inject.Inject;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.github.ldeitos.validation.MessagesSource;
import com.github.ldeitos.validation.impl.interpolator.PreInterpolator;
import com.github.ldeitos.validators.util.ConstraintBuilderAdapter;
import com.github.ldeitos.validators.util.NodeBuilderDefinedContextAdapter;
import com.github.ldeitos.validators.util.Path;
import com.github.ldeitos.validators.util.PathBuilder;

/**
 * Abstraction to provide easy way to create validator with multiple violation
 * registers.
 *
 * @author Leandro Deitos
 *
 * @param 
 *            Constraint annotation
 * @param 
 *            Type in validation.
 *
 * @since 0.8.0
 */
public abstract class AbstractExtendedValidator implements ConstraintValidator {

	private Logger log = LoggerFactory.getLogger(AbstractExtendedValidator.class);

	private ThreadLocal validMap = new ThreadLocal();

	private ThreadLocal pathBuilder = new ThreadLocal();

	private ThreadLocal contextMap = new ThreadLocal();

	@Inject
	private PreInterpolator preInterpolator;

	/**
	 * {@inheritDoc}
*
* P.S.: Can't be override by concrete implementations. */ @Override public final boolean isValid(T value, ConstraintValidatorContext context) { Boolean valid = true; contextMap.set(context); validMap.set(valid); try { doValidation(value); valid = validMap.get(); if (!valid) { String logMsg = "[%s] value are invalided by [%s] validator call."; log.info(format(logMsg, value, this.getClass().getName())); } } finally { release(); } return valid; } /** * Extension point to implement validation code.
* This code is invoked during API * {@link #isValid(Object, ConstraintValidatorContext)} call and violation * must be registered by any methods below: *
    *
  • {@link #addViolation(String, String...)}
  • *
  • {@link #addViolationWithDefaultTemplate(String...)}
  • *
* * @param value * Value in validation. * * @since 0.8.0 */ public abstract void doValidation(T value); /** * Makes value on validation invalid and add a violation with default * message template, defined in Constraint annotation, and parameters to * interpolate. * * @param msgParameters * Parameters to be interpolated in message violation.
* Can be informed in "value" pattern, to be interpolated in * indexed parameter like "My {0} message" or in "key=value" * pattern, to be interpolated in defined parameter like * "My {par} message". * * @since 0.8.0 */ protected void addViolationWithDefaultTemplate(String... msgParameters) { ConstraintValidatorContext context = contextMap.get(); String msg = context.getDefaultConstraintMessageTemplate(); String msgInterpolated = preInterpolator.interpolate(msg, msgParameters); msgInterpolated = format(msg, msgInterpolated); log.debug("Adding violation with default template."); doTraceLog(msgParameters); makeInvalid(); context.buildConstraintViolationWithTemplate(msgInterpolated).addConstraintViolation(); } /** * Makes value on validation invalid and add a violation with default * message template, defined in Constraint annotation, and parameters to * interpolate. * * @param atPath * Full {@link Path} to origin of violation in object being * validated. * * @param msgParameters * Parameters to be interpolated in message violation.
* Can be informed in "value" pattern, to be interpolated in * indexed parameter like "My {0} message" or in "key=value" * pattern, to be interpolated in defined parameter like * "My {par} message". * * @since 0.9.0 */ protected void addViolationWithDefaultTemplate(Path atPath, String... msgParameters) { ConstraintValidatorContext context = contextMap.get(); String msg = context.getDefaultConstraintMessageTemplate(); String msgInterpolated = preInterpolator.interpolate(msg, msgParameters); msgInterpolated = format(msg, msgInterpolated); log.debug("Adding violation with default template."); doTraceLog(msgParameters); makeInvalid(); ConstraintViolationBuilder cvBuilder = context.buildConstraintViolationWithTemplate(msgInterpolated); buildPath(cvBuilder, atPath).addConstraintViolation(); } /** * Makes value on validation invalid and add a violation with default * message template, defined in Constraint annotation, and parameters to * interpolate. * * @param pathBuilder * {@link PathBuilder} to build {@link Path} reference. * * @param msgParameters * Parameters to be interpolated in message violation.
* Can be informed in "value" pattern, to be interpolated in * indexed parameter like "My {0} message" or in "key=value" * pattern, to be interpolated in defined parameter like * "My {par} message". * * @since 0.9.0 */ protected void addViolationWithDefaultTemplate(PathBuilder pathBuilder, String... msgParameters) { addViolationWithDefaultTemplate(pathBuilder.getPath(), msgParameters); } /** * Makes value on validation invalid and add a violation with informed * message template and parameters to interpolate. * * @param msgTemplate * Message template can be:
* - Just message text, like "My message";
* - Message text with parameters, like "My {0} message" or * "My {par} message";
* - Message key to get message in parameterized * {@link MessagesSource}, like {my.message.key}. * @param msgParameters * Parameters to be interpolated in message violation.
* Can be informed in "value" pattern, to be interpolated in * indexed parameter like "My {0} message" or in "key=value" * pattern, to be interpolated in defined parameter like * "My {par} message". * * @since 0.8.0 */ protected void addViolation(String msgTemplate, String... msgParameters) { ConstraintValidatorContext context = contextMap.get(); String msgInterpolated = preInterpolator.interpolate(msgTemplate, msgParameters); msgInterpolated = format(msgTemplate, msgInterpolated); log.debug(format("Adding violation with [%s] template.", msgTemplate)); doTraceLog(msgParameters); makeInvalid(); context.buildConstraintViolationWithTemplate(msgInterpolated).addConstraintViolation(); } /** * Makes value on validation invalid and add a violation with informed * message template and parameters to interpolate. * * @param path * Full {@link Path} to origin of violation in object being * validated. * * @param msgTemplate * Message template can be:
* - Just message text, like "My message";
* - Message text with parameters, like "My {0} message" or * "My {par} message";
* - Message key to get message in parameterized * {@link MessagesSource}, like {my.message.key}. * @param msgParameters * Parameters to be interpolated in message violation.
* Can be informed in "value" pattern, to be interpolated in * indexed parameter like "My {0} message" or in "key=value" * pattern, to be interpolated in defined parameter like * "My {par} message". * * @since 0.9.0 */ protected void addViolation(Path path, String msgTemplate, String... msgParameters) { ConstraintValidatorContext context = contextMap.get(); String msgInterpolated = preInterpolator.interpolate(msgTemplate, msgParameters); msgInterpolated = format(msgTemplate, msgInterpolated); log.debug(format("Adding violation with [%s] template.", msgTemplate)); doTraceLog(msgParameters); makeInvalid(); ConstraintViolationBuilder vBuilder = context.buildConstraintViolationWithTemplate(msgInterpolated); buildPath(vBuilder, path).addConstraintViolation(); } /** * Makes value on validation invalid and add a violation with informed * message template and parameters to interpolate. * * @param pathBuilder * {@link PathBuilder} to build {@link Path} reference. * * @param msgTemplate * Message template can be:
* - Just message text, like "My message";
* - Message text with parameters, like "My {0} message" or * "My {par} message";
* - Message key to get message in parameterized * {@link MessagesSource}, like {my.message.key}. * @param msgParameters * Parameters to be interpolated in message violation.
* Can be informed in "value" pattern, to be interpolated in * indexed parameter like "My {0} message" or in "key=value" * pattern, to be interpolated in defined parameter like * "My {par} message". * * @since 0.9.0 */ protected void addViolation(PathBuilder pathBuilder, String msgTemplate, String... msgParameters) { addViolation(pathBuilder.getPath(), msgTemplate, msgParameters); } private void doTraceLog(String[] msgParameters) { if (log.isTraceEnabled()) { for (int i = 0; i < msgParameters.length; i++) { String parameter = msgParameters[i]; log.trace(format("Parameter #%d: %s", i, parameter)); } } } private void makeInvalid() { if (validMap.get()) { ConstraintValidatorContext context = contextMap.get(); validMap.set(false); context.disableDefaultConstraintViolation(); log.debug("Value marked as invalid."); } } /** * Builder to describe a simple {@link Path} to violation origin. * * Usage examples: * *
	 * //assuming the following domain model
	 * public class Address {
	 *     public String getStreet() { ... }
	 *     public Country getCountry() { ... }
	 * }
	 *
	 * //From a class level constraint on Address
	 * //Build a constraint violation on the default path + "street"
	 * //i.e. the street property of Address
	 * buildPath("street");
	 *
	 * @param path
	 * 		String representation of path to property to register violation.
	 * @return
	 * 		{@link PathBuilder} to build a {@link Path} reference.
	 *
	 * @since 0.9.0
	 */
	protected PathBuilder buildPath(String path) {
		PathBuilder pBuilder = pathBuilder.get();
		if (pBuilder == null) {
			pBuilder = new PathBuilder();
			pathBuilder.set(pBuilder);
		}

		return pBuilder.add(path);
	}

	/**
	 * Builder to describe a mapped {@link Path} to violation origin.
	 *
	 * Usage examples:
	 *
	 * 
	 * //assuming the following domain model
	 * public class User {
	 *     public Map getAddresses() { ... }
	 * }
	 * 
	 * public class Address {
	 *     public String getStreet() { ... }
	 *     public Country getCountry() { ... }
	 * }
	 * 
	 * public class Country {
	 *     public String getName() { ... }
	 * }
	 * 
	 * //From a class level constraint on User
	 * //Build a constraint violation on the default path + addresses["home"].country.name
	 * //i.e. property "country.name" on the object stored under "home" in the map
	 * buildPath("addresses", "home").add("country").add("name")
* or
* buildPath("addresses[home].country.name")

* * P.S.: A full path build like buildPath("addresses[home].country.name"), when refers a map, * only can be used when a map key is a String, in other way, uses a fluent model, like * buildPath("addresses", aObject).add("country").add("name"). * * * @param path * String representation of path to property to register violation. * @param key * Key to mapped content to registered violation. * @return * {@link PathBuilder} to build a {@link Path} reference. * * @since 0.9.1 */ protected PathBuilder buildPath(String path, Object key) { PathBuilder pBuilder = pathBuilder.get(); if (pBuilder == null) { pBuilder = new PathBuilder(); pathBuilder.set(pBuilder); } return pBuilder.add(path, key); } /** * Return a {@link Path} to property level mapped content.
* Usage examples: * *
	 * //assuming the following domain model
	 * public class User {
	 *     public Map getAddresses() { ... }
	 * }
	 * 
	 * public class Address {
	 *     public String getStreet() { ... }
	 *     public Country getCountry() { ... }
	 * }
	 * 
	 * //From a property-level constraint on User.addresses
	 * //Build a constraint violation on the default path + the bean stored
	 * //under the "home" key on map:
	 *  atKey("home")
	 * 
	 * @param key
	 * 		Key to mapped collection content to registered violation.
	 * @return
	 * 		Correspondent {@link Path}.
	 *
	 * @since 0.9.1
	 */
	protected Path atKey(Object key) {
		PathBuilder pBuilder = pathBuilder.get();
		if (pBuilder == null) {
			pBuilder = new PathBuilder();
			pathBuilder.set(pBuilder);
		}

		return pBuilder.addAtKey(key).getPath();
	}

	/**
	 * Builder to describe a indexed {@link Path} to violation origin.
	 *
	 * Usage examples:
	 *
	 * 
	 * //assuming the following domain model
	 * public class User {
	 *     public List getAddresses() { ... }
	 * }
	 * 
	 * public class Address {
	 *     public String getStreet() { ... }
	 *     public Country getCountry() { ... }
	 * }
	 * 
	 * //From a class level constraint on User
	 * //Build a constraint violation on the default path + addresses(2).country.name
	 * //i.e. property "country.name" on the object stored under index 2 in the list
	 * buildPath("addresses", 2).add("country").add("name")
* or
* buildPath("addresses(2).country.name")

* * * @param path * String representation of path to property to register violation. * @param index * Index to indexed collection content to registered violation. * @return * {@link PathBuilder} to build a {@link Path} reference. * * @since 0.9.0 */ protected PathBuilder buildPath(String path, Integer index) { PathBuilder pBuilder = pathBuilder.get(); if (pBuilder == null) { pBuilder = new PathBuilder(); pathBuilder.set(pBuilder); } return pBuilder.add(path, index); } /** * Return a {@link Path} to property level indexed collection content.
* Usage examples: * *
	 * //assuming the following domain model
	 * public class User {
	 *     public List getAddresses() { ... }
	 * }
	 * 
	 * public class Address {
	 *     public String getStreet() { ... }
	 *     public Country getCountry() { ... }
	 * }
	 * 
	 * //From a property-level constraint on User.addresses
	 * //Build a constraint violation on the default path + the bean stored
	 * //under the index 2 on list:
	 *  atIndex(2)
	 * 
	 * @param index
	 * 		Index to indexed collection content to registered violation.
	 * @return
	 * 		Correspondent {@link Path}.
	 *
	 * @since 0.9.0
	 */
	protected Path atIndex(Integer index) {
		PathBuilder pBuilder = pathBuilder.get();
		if (pBuilder == null) {
			pBuilder = new PathBuilder();
			pathBuilder.set(pBuilder);
		}

		return pBuilder.addAtIndex(index).getPath();
	}

	private ConstraintBuilderAdapter buildPath(ConstraintViolationBuilder cvBuilder, Path path) {
		ConstraintBuilderAdapter constraintBuilderAdapter = new NodeBuilderDefinedContextAdapter(
			cvBuilder.addNode(path.getPath()));

		while (path.hasNext()) {
			path = path.getNext();
			constraintBuilderAdapter = constraintBuilderAdapter.addPropertyNode(path);
		}

		return constraintBuilderAdapter;
	}

	private void release() {
		validMap.remove();
		contextMap.remove();
		pathBuilder.remove();
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy