Formalist extension added (Apache Bval lisbrary wrapper)
parent
ed862ec2fe
commit
0d85d73c9b
@ -0,0 +1,82 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>org.pagan.quarkus</groupId>
|
||||||
|
<artifactId>janitor-parent</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>formalist-deployment</artifactId>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.quarkus</groupId>
|
||||||
|
<artifactId>quarkus-core-deployment</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.quarkus</groupId>
|
||||||
|
<artifactId>quarkus-arc-deployment</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.quarkus</groupId>
|
||||||
|
<artifactId>quarkus-resteasy-server-common-spi</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>jakarta.validation</groupId>
|
||||||
|
<artifactId>jakarta.validation-api</artifactId>
|
||||||
|
<version>2.0.2</version>
|
||||||
|
<type>jar</type>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- <dependency>
|
||||||
|
<groupId>io.quarkus</groupId>
|
||||||
|
<artifactId>quarkus-junit5-internal</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.assertj</groupId>
|
||||||
|
<artifactId>assertj-core</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.quarkus</groupId>
|
||||||
|
<artifactId>quarkus-resteasy-jsonb-deployment</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.rest-assured</groupId>
|
||||||
|
<artifactId>rest-assured</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.pagan.quarkus</groupId>
|
||||||
|
<artifactId>formalist</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>${compiler-plugin.version}</version>
|
||||||
|
<configuration>
|
||||||
|
<annotationProcessorPaths>
|
||||||
|
<path>
|
||||||
|
<groupId>io.quarkus</groupId>
|
||||||
|
<artifactId>quarkus-extension-processor</artifactId>
|
||||||
|
<version>${quarkus.platform.version}</version>
|
||||||
|
</path>
|
||||||
|
</annotationProcessorPaths>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
||||||
@ -0,0 +1,350 @@
|
|||||||
|
package org.pagan.formalist;
|
||||||
|
|
||||||
|
import static io.quarkus.deployment.annotations.ExecutionTime.STATIC_INIT;
|
||||||
|
|
||||||
|
import java.lang.annotation.Repeatable;
|
||||||
|
import java.lang.reflect.Modifier;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import javax.validation.ClockProvider;
|
||||||
|
import javax.validation.Constraint;
|
||||||
|
import javax.validation.ConstraintValidator;
|
||||||
|
import javax.validation.ConstraintValidatorFactory;
|
||||||
|
import javax.validation.MessageInterpolator;
|
||||||
|
import javax.validation.ParameterNameProvider;
|
||||||
|
import javax.validation.TraversableResolver;
|
||||||
|
import javax.validation.Valid;
|
||||||
|
import javax.validation.executable.ValidateOnExecution;
|
||||||
|
import javax.validation.valueextraction.ValueExtractor;
|
||||||
|
|
||||||
|
import org.jboss.jandex.AnnotationInstance;
|
||||||
|
import org.jboss.jandex.AnnotationTarget;
|
||||||
|
import org.jboss.jandex.ClassInfo;
|
||||||
|
import org.jboss.jandex.CompositeIndex;
|
||||||
|
import org.jboss.jandex.DotName;
|
||||||
|
import org.jboss.jandex.IndexView;
|
||||||
|
import org.jboss.jandex.MethodInfo;
|
||||||
|
import org.jboss.jandex.Type;
|
||||||
|
|
||||||
|
import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
|
||||||
|
import io.quarkus.arc.deployment.AnnotationsTransformerBuildItem;
|
||||||
|
import io.quarkus.arc.deployment.BeanArchiveIndexBuildItem;
|
||||||
|
import io.quarkus.arc.deployment.BeanContainerListenerBuildItem;
|
||||||
|
import io.quarkus.arc.deployment.UnremovableBeanBuildItem;
|
||||||
|
import io.quarkus.arc.processor.BeanInfo;
|
||||||
|
import io.quarkus.deployment.Capabilities;
|
||||||
|
import io.quarkus.deployment.Capability;
|
||||||
|
import io.quarkus.deployment.Feature;
|
||||||
|
import io.quarkus.deployment.annotations.BuildProducer;
|
||||||
|
import io.quarkus.deployment.annotations.BuildStep;
|
||||||
|
import io.quarkus.deployment.annotations.Record;
|
||||||
|
import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
|
||||||
|
import io.quarkus.deployment.builditem.FeatureBuildItem;
|
||||||
|
import io.quarkus.deployment.builditem.HotDeploymentWatchedFileBuildItem;
|
||||||
|
import io.quarkus.deployment.builditem.ShutdownContextBuildItem;
|
||||||
|
import io.quarkus.deployment.builditem.nativeimage.NativeImageConfigBuildItem;
|
||||||
|
import io.quarkus.deployment.builditem.nativeimage.ReflectiveFieldBuildItem;
|
||||||
|
import io.quarkus.deployment.builditem.nativeimage.ReflectiveMethodBuildItem;
|
||||||
|
import io.quarkus.deployment.logging.LogCleanupFilterBuildItem;
|
||||||
|
import io.quarkus.deployment.recording.RecorderContext;
|
||||||
|
//import org.pagan.formalist.HibernateValidatorBuildTimeConfig;
|
||||||
|
import org.pagan.formalist.HibernateValidatorRecorder;
|
||||||
|
import org.pagan.formalist.ValidatorProvider;
|
||||||
|
import org.pagan.formalist.interceptor.MethodValidationInterceptor;
|
||||||
|
import io.quarkus.resteasy.server.common.spi.AdditionalJaxRsResourceMethodAnnotationsBuildItem;
|
||||||
|
//import io.quarkus.runtime.LocalesBuildTimeConfig;
|
||||||
|
|
||||||
|
class HibernateValidatorProcessor {
|
||||||
|
|
||||||
|
private static final String META_INF_VALIDATION_XML = "META-INF/validation.xml";
|
||||||
|
|
||||||
|
private static final DotName CONSTRAINT_VALIDATOR_FACTORY = DotName
|
||||||
|
.createSimple(ConstraintValidatorFactory.class.getName());
|
||||||
|
private static final DotName MESSAGE_INTERPOLATOR = DotName.createSimple(MessageInterpolator.class.getName());
|
||||||
|
// private static final DotName LOCALE_RESOLVER =
|
||||||
|
// DotName.createSimple(LocaleResolver.class.getName());
|
||||||
|
|
||||||
|
private static final DotName TRAVERSABLE_RESOLVER = DotName.createSimple(TraversableResolver.class.getName());
|
||||||
|
private static final DotName PARAMETER_NAME_PROVIDER = DotName.createSimple(ParameterNameProvider.class.getName());
|
||||||
|
private static final DotName CLOCK_PROVIDER = DotName.createSimple(ClockProvider.class.getName());
|
||||||
|
// private static final DotName SCRIPT_EVALUATOR_FACTORY =
|
||||||
|
// DotName.createSimple(ScriptEvaluatorFactory.class.getName());
|
||||||
|
// private static final DotName GETTER_PROPERTY_SELECTION_STRATEGY = DotName
|
||||||
|
// .createSimple(GetterPropertySelectionStrategy.class.getName());
|
||||||
|
|
||||||
|
private static final DotName CONSTRAINT_VALIDATOR = DotName.createSimple(ConstraintValidator.class.getName());
|
||||||
|
private static final DotName VALUE_EXTRACTOR = DotName.createSimple(ValueExtractor.class.getName());
|
||||||
|
|
||||||
|
private static final DotName VALIDATE_ON_EXECUTION = DotName.createSimple(ValidateOnExecution.class.getName());
|
||||||
|
|
||||||
|
private static final DotName VALID = DotName.createSimple(Valid.class.getName());
|
||||||
|
|
||||||
|
private static final DotName REPEATABLE = DotName.createSimple(Repeatable.class.getName());
|
||||||
|
|
||||||
|
private static final Pattern BUILT_IN_CONSTRAINT_REPEATABLE_CONTAINER_PATTERN = Pattern.compile("\\$List$");
|
||||||
|
|
||||||
|
// @BuildStep
|
||||||
|
// HotDeploymentWatchedFileBuildItem configFile() {
|
||||||
|
// return new HotDeploymentWatchedFileBuildItem(META_INF_VALIDATION_XML);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// @BuildStep
|
||||||
|
// LogCleanupFilterBuildItem logCleanup() {
|
||||||
|
// return new
|
||||||
|
// LogCleanupFilterBuildItem("org.hibernate.validator.internal.util.Version",
|
||||||
|
// "HV000001:");
|
||||||
|
// }
|
||||||
|
|
||||||
|
@BuildStep
|
||||||
|
void registerAdditionalBeans(BuildProducer<AdditionalBeanBuildItem> additionalBeans,
|
||||||
|
BuildProducer<UnremovableBeanBuildItem> unremovableBean, Capabilities capabilities) {
|
||||||
|
// The bean encapsulating the Validator and ValidatorFactory
|
||||||
|
additionalBeans.produce(new AdditionalBeanBuildItem(ValidatorProvider.class));
|
||||||
|
|
||||||
|
// The CDI interceptor which will validate the methods annotated with
|
||||||
|
// @MethodValidated
|
||||||
|
additionalBeans.produce(new AdditionalBeanBuildItem(MethodValidationInterceptor.class));
|
||||||
|
|
||||||
|
if (capabilities.isPresent(Capability.RESTEASY)) {
|
||||||
|
// The CDI interceptor which will validate the methods annotated with
|
||||||
|
// @JaxrsEndPointValidated
|
||||||
|
additionalBeans.produce(
|
||||||
|
new AdditionalBeanBuildItem("org.pagan.formalist.jaxrs.JaxrsEndPointValidationInterceptor"));
|
||||||
|
// additionalBeans.produce(new AdditionalBeanBuildItem(
|
||||||
|
// "io.quarkus.hibernate.validator.runtime.jaxrs.ResteasyContextLocaleResolver"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not remove the Bean Validation beans
|
||||||
|
unremovableBean.produce(new UnremovableBeanBuildItem(new Predicate<BeanInfo>() {
|
||||||
|
@Override
|
||||||
|
public boolean test(BeanInfo beanInfo) {
|
||||||
|
return beanInfo.hasType(CONSTRAINT_VALIDATOR) || beanInfo.hasType(CONSTRAINT_VALIDATOR_FACTORY)
|
||||||
|
|| beanInfo.hasType(MESSAGE_INTERPOLATOR) || beanInfo.hasType(TRAVERSABLE_RESOLVER)
|
||||||
|
|| beanInfo.hasType(PARAMETER_NAME_PROVIDER) || beanInfo.hasType(CLOCK_PROVIDER)
|
||||||
|
|| beanInfo.hasType(VALUE_EXTRACTOR);
|
||||||
|
// || beanInfo.hasType(SCRIPT_EVALUATOR_FACTORY)
|
||||||
|
// || beanInfo.hasType(GETTER_PROPERTY_SELECTION_STRATEGY)
|
||||||
|
// || beanInfo.hasType(LOCALE_RESOLVER);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
@BuildStep
|
||||||
|
@Record(STATIC_INIT)
|
||||||
|
public void build(HibernateValidatorRecorder recorder, RecorderContext recorderContext,
|
||||||
|
BuildProducer<ReflectiveFieldBuildItem> reflectiveFields,
|
||||||
|
BuildProducer<ReflectiveMethodBuildItem> reflectiveMethods,
|
||||||
|
BuildProducer<AnnotationsTransformerBuildItem> annotationsTransformers,
|
||||||
|
BeanArchiveIndexBuildItem beanArchiveIndexBuildItem, CombinedIndexBuildItem combinedIndexBuildItem,
|
||||||
|
BuildProducer<FeatureBuildItem> feature,
|
||||||
|
BuildProducer<BeanContainerListenerBuildItem> beanContainerListener,
|
||||||
|
ShutdownContextBuildItem shutdownContext,
|
||||||
|
List<AdditionalJaxRsResourceMethodAnnotationsBuildItem> additionalJaxRsResourceMethodAnnotations,
|
||||||
|
Capabilities capabilities
|
||||||
|
// ,LocalesBuildTimeConfig localesBuildTimeConfig
|
||||||
|
// ,HibernateValidatorBuildTimeConfig hibernateValidatorBuildTimeConfig
|
||||||
|
) throws Exception {
|
||||||
|
|
||||||
|
feature.produce(new FeatureBuildItem("formalist"));
|
||||||
|
|
||||||
|
// we use both indexes to support both generated beans and jars that contain no
|
||||||
|
// CDI beans but only Validation annotations
|
||||||
|
IndexView indexView = CompositeIndex.create(beanArchiveIndexBuildItem.getIndex(),
|
||||||
|
combinedIndexBuildItem.getIndex());
|
||||||
|
|
||||||
|
Set<DotName> consideredAnnotations = new HashSet<>();
|
||||||
|
|
||||||
|
// Set<String> builtinConstraints = ConstraintHelper.getBuiltinConstraints();
|
||||||
|
|
||||||
|
// Collect the constraint annotations provided by Hibernate Validator and Bean
|
||||||
|
// Validation
|
||||||
|
// contributeBuiltinConstraints(builtinConstraints, consideredAnnotations);
|
||||||
|
|
||||||
|
// Add the constraint annotations present in the application itself
|
||||||
|
for (AnnotationInstance constraint : indexView
|
||||||
|
.getAnnotations(DotName.createSimple(Constraint.class.getName()))) {
|
||||||
|
consideredAnnotations.add(constraint.target().asClass().name());
|
||||||
|
|
||||||
|
if (constraint.target().asClass().annotations().containsKey(REPEATABLE)) {
|
||||||
|
for (AnnotationInstance repeatableConstraint : constraint.target().asClass().annotations()
|
||||||
|
.get(REPEATABLE)) {
|
||||||
|
consideredAnnotations.add(repeatableConstraint.value().asClass().name());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also consider elements that are marked with @Valid
|
||||||
|
consideredAnnotations.add(VALID);
|
||||||
|
|
||||||
|
// Also consider elements that are marked with @ValidateOnExecution
|
||||||
|
consideredAnnotations.add(VALIDATE_ON_EXECUTION);
|
||||||
|
|
||||||
|
Set<DotName> classNamesToBeValidated = new HashSet<>();
|
||||||
|
Map<DotName, Set<String>> inheritedAnnotationsToBeValidated = new HashMap<>();
|
||||||
|
Set<String> detectedBuiltinConstraints = new HashSet<>();
|
||||||
|
|
||||||
|
for (DotName consideredAnnotation : consideredAnnotations) {
|
||||||
|
Collection<AnnotationInstance> annotationInstances = indexView.getAnnotations(consideredAnnotation);
|
||||||
|
|
||||||
|
if (annotationInstances.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// we trim the repeatable container suffix if needed
|
||||||
|
String builtinConstraintCandidate = BUILT_IN_CONSTRAINT_REPEATABLE_CONTAINER_PATTERN
|
||||||
|
.matcher(consideredAnnotation.toString()).replaceAll("");
|
||||||
|
// if (builtinConstraints.contains(builtinConstraintCandidate)) {
|
||||||
|
// detectedBuiltinConstraints.add(builtinConstraintCandidate);
|
||||||
|
// }
|
||||||
|
|
||||||
|
for (AnnotationInstance annotation : annotationInstances) {
|
||||||
|
if (annotation.target().kind() == AnnotationTarget.Kind.FIELD) {
|
||||||
|
contributeClass(classNamesToBeValidated, indexView,
|
||||||
|
annotation.target().asField().declaringClass().name());
|
||||||
|
reflectiveFields.produce(new ReflectiveFieldBuildItem(annotation.target().asField()));
|
||||||
|
contributeClassMarkedForCascadingValidation(classNamesToBeValidated, indexView,
|
||||||
|
consideredAnnotation, annotation.target().asField().type());
|
||||||
|
} else if (annotation.target().kind() == AnnotationTarget.Kind.METHOD) {
|
||||||
|
contributeClass(classNamesToBeValidated, indexView,
|
||||||
|
annotation.target().asMethod().declaringClass().name());
|
||||||
|
// we need to register the method for reflection as it could be a getter
|
||||||
|
reflectiveMethods.produce(new ReflectiveMethodBuildItem(annotation.target().asMethod()));
|
||||||
|
contributeClassMarkedForCascadingValidation(classNamesToBeValidated, indexView,
|
||||||
|
consideredAnnotation, annotation.target().asMethod().returnType());
|
||||||
|
contributeMethodsWithInheritedValidation(inheritedAnnotationsToBeValidated, indexView,
|
||||||
|
annotation.target().asMethod());
|
||||||
|
} else if (annotation.target().kind() == AnnotationTarget.Kind.METHOD_PARAMETER) {
|
||||||
|
contributeClass(classNamesToBeValidated, indexView,
|
||||||
|
annotation.target().asMethodParameter().method().declaringClass().name());
|
||||||
|
// a getter does not have parameters so it's a pure method: no need for
|
||||||
|
// reflection in this case
|
||||||
|
contributeClassMarkedForCascadingValidation(classNamesToBeValidated, indexView,
|
||||||
|
consideredAnnotation,
|
||||||
|
// FIXME this won't work in the case of synthetic parameters
|
||||||
|
annotation.target().asMethodParameter().method().parameters()
|
||||||
|
.get(annotation.target().asMethodParameter().position()));
|
||||||
|
contributeMethodsWithInheritedValidation(inheritedAnnotationsToBeValidated, indexView,
|
||||||
|
annotation.target().asMethodParameter().method());
|
||||||
|
} else if (annotation.target().kind() == AnnotationTarget.Kind.CLASS) {
|
||||||
|
contributeClass(classNamesToBeValidated, indexView, annotation.target().asClass().name());
|
||||||
|
// no need for reflection in the case of a class level constraint
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the annotations transformer to add @MethodValidated annotations on the
|
||||||
|
// methods requiring validation
|
||||||
|
Set<DotName> additionalJaxRsMethodAnnotationsDotNames = new HashSet<>(
|
||||||
|
additionalJaxRsResourceMethodAnnotations.size());
|
||||||
|
for (AdditionalJaxRsResourceMethodAnnotationsBuildItem additionalJaxRsResourceMethodAnnotation : additionalJaxRsResourceMethodAnnotations) {
|
||||||
|
additionalJaxRsMethodAnnotationsDotNames
|
||||||
|
.addAll(additionalJaxRsResourceMethodAnnotation.getAnnotationClasses());
|
||||||
|
}
|
||||||
|
annotationsTransformers.produce(new AnnotationsTransformerBuildItem(new MethodValidatedAnnotationsTransformer(
|
||||||
|
consideredAnnotations, additionalJaxRsMethodAnnotationsDotNames, inheritedAnnotationsToBeValidated)));
|
||||||
|
|
||||||
|
Set<Class<?>> classesToBeValidated = new HashSet<>();
|
||||||
|
for (DotName className : classNamesToBeValidated) {
|
||||||
|
classesToBeValidated.add(recorderContext.classProxy(className.toString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
beanContainerListener
|
||||||
|
.produce(new BeanContainerListenerBuildItem(recorder.initializeValidatorFactory(classesToBeValidated,
|
||||||
|
detectedBuiltinConstraints, hasXmlConfiguration(),
|
||||||
|
// capabilities.isPresent(Capability.HIBERNATE_ORM),
|
||||||
|
shutdownContext
|
||||||
|
// ,localesBuildTimeConfig
|
||||||
|
// ,hibernateValidatorBuildTimeConfig
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// @BuildStep
|
||||||
|
// NativeImageConfigBuildItem nativeImageConfig() {
|
||||||
|
// return NativeImageConfigBuildItem.builder()
|
||||||
|
// .addResourceBundle(AbstractMessageInterpolator.DEFAULT_VALIDATION_MESSAGES)
|
||||||
|
// .addResourceBundle(AbstractMessageInterpolator.USER_VALIDATION_MESSAGES)
|
||||||
|
// .addResourceBundle(AbstractMessageInterpolator.CONTRIBUTOR_VALIDATION_MESSAGES)
|
||||||
|
// .build();
|
||||||
|
// }
|
||||||
|
|
||||||
|
private static void contributeBuiltinConstraints(Set<String> builtinConstraints,
|
||||||
|
Set<DotName> consideredAnnotationsCollector) {
|
||||||
|
for (String builtinConstraint : builtinConstraints) {
|
||||||
|
consideredAnnotationsCollector.add(DotName.createSimple(builtinConstraint));
|
||||||
|
|
||||||
|
// for all built-in constraints, we follow a strict convention for repeatable
|
||||||
|
// annotations,
|
||||||
|
// they are all inner classes called List
|
||||||
|
// while not all our built-in constraints are repeatable, let's avoid loading
|
||||||
|
// the class to check
|
||||||
|
consideredAnnotationsCollector.add(DotName.createSimple(builtinConstraint + "$List"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void contributeClass(Set<DotName> classNamesCollector, IndexView indexView, DotName className) {
|
||||||
|
classNamesCollector.add(className);
|
||||||
|
for (ClassInfo subclass : indexView.getAllKnownSubclasses(className)) {
|
||||||
|
if (Modifier.isAbstract(subclass.flags())) {
|
||||||
|
// we can avoid adding the abstract classes here: either they are parent classes
|
||||||
|
// and they will be dealt with by Hibernate Validator or they are child classes
|
||||||
|
// without any proper implementation and we can ignore them.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
classNamesCollector.add(subclass.name());
|
||||||
|
}
|
||||||
|
for (ClassInfo implementor : indexView.getAllKnownImplementors(className)) {
|
||||||
|
if (Modifier.isAbstract(implementor.flags())) {
|
||||||
|
// we can avoid adding the abstract classes here: either they are parent classes
|
||||||
|
// and they will be dealt with by Hibernate Validator or they are child classes
|
||||||
|
// without any proper implementation and we can ignore them.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
classNamesCollector.add(implementor.name());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void contributeClassMarkedForCascadingValidation(Set<DotName> classNamesCollector,
|
||||||
|
IndexView indexView, DotName consideredAnnotation, Type type) {
|
||||||
|
if (VALID != consideredAnnotation) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DotName className = getClassName(type);
|
||||||
|
if (className != null) {
|
||||||
|
contributeClass(classNamesCollector, indexView, className);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void contributeMethodsWithInheritedValidation(
|
||||||
|
Map<DotName, Set<String>> inheritedAnnotationsToBeValidated, IndexView indexView, MethodInfo method) {
|
||||||
|
ClassInfo clazz = method.declaringClass();
|
||||||
|
if (Modifier.isInterface(clazz.flags())) {
|
||||||
|
// Remember annotated interface methods that must be validated
|
||||||
|
inheritedAnnotationsToBeValidated.computeIfAbsent(clazz.name(), k -> new HashSet<String>())
|
||||||
|
.add(method.name().toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DotName getClassName(Type type) {
|
||||||
|
switch (type.kind()) {
|
||||||
|
case CLASS:
|
||||||
|
case PARAMETERIZED_TYPE:
|
||||||
|
return type.name();
|
||||||
|
case ARRAY:
|
||||||
|
return getClassName(type.asArrayType().component());
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean hasXmlConfiguration() {
|
||||||
|
return Thread.currentThread().getContextClassLoader().getResource(META_INF_VALIDATION_XML) != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,111 @@
|
|||||||
|
package org.pagan.formalist;
|
||||||
|
|
||||||
|
import java.lang.reflect.Modifier;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.jboss.jandex.AnnotationTarget.Kind;
|
||||||
|
import org.jboss.jandex.ClassInfo;
|
||||||
|
import org.jboss.jandex.DotName;
|
||||||
|
import org.jboss.jandex.MethodInfo;
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
|
import io.quarkus.arc.processor.AnnotationsTransformer;
|
||||||
|
import org.pagan.formalist.annotataions.MethodValidated;
|
||||||
|
import org.pagan.formalist.annotataions.JaxrsEndPointValidated;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add {@link MethodValidated} annotations to the methods requiring validation.
|
||||||
|
*/
|
||||||
|
public class MethodValidatedAnnotationsTransformer implements AnnotationsTransformer {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = Logger.getLogger(MethodValidatedAnnotationsTransformer.class.getPackage().getName());
|
||||||
|
|
||||||
|
private static final DotName[] JAXRS_METHOD_ANNOTATIONS = {
|
||||||
|
DotName.createSimple("javax.ws.rs.GET"),
|
||||||
|
DotName.createSimple("javax.ws.rs.HEAD"),
|
||||||
|
DotName.createSimple("javax.ws.rs.DELETE"),
|
||||||
|
DotName.createSimple("javax.ws.rs.OPTIONS"),
|
||||||
|
DotName.createSimple("javax.ws.rs.PATCH"),
|
||||||
|
DotName.createSimple("javax.ws.rs.POST"),
|
||||||
|
DotName.createSimple("javax.ws.rs.PUT"),
|
||||||
|
};
|
||||||
|
|
||||||
|
private final Set<DotName> consideredAnnotations;
|
||||||
|
private final Collection<DotName> effectiveJaxRsMethodDefiningAnnotations;
|
||||||
|
private final Map<DotName, Set<String>> inheritedAnnotationsToBeValidated;
|
||||||
|
|
||||||
|
MethodValidatedAnnotationsTransformer(Set<DotName> consideredAnnotations,
|
||||||
|
Collection<DotName> additionalJaxRsMethodAnnotationsDotNames,
|
||||||
|
Map<DotName, Set<String>> inheritedAnnotationsToBeValidated) {
|
||||||
|
this.consideredAnnotations = consideredAnnotations;
|
||||||
|
this.inheritedAnnotationsToBeValidated = inheritedAnnotationsToBeValidated;
|
||||||
|
|
||||||
|
this.effectiveJaxRsMethodDefiningAnnotations = new ArrayList<>(
|
||||||
|
JAXRS_METHOD_ANNOTATIONS.length + additionalJaxRsMethodAnnotationsDotNames.size());
|
||||||
|
effectiveJaxRsMethodDefiningAnnotations.addAll(Arrays.asList(JAXRS_METHOD_ANNOTATIONS));
|
||||||
|
effectiveJaxRsMethodDefiningAnnotations.addAll(additionalJaxRsMethodAnnotationsDotNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean appliesTo(Kind kind) {
|
||||||
|
return Kind.METHOD == kind;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void transform(TransformationContext transformationContext) {
|
||||||
|
MethodInfo method = transformationContext.getTarget().asMethod();
|
||||||
|
|
||||||
|
if (requiresValidation(method)) {
|
||||||
|
if (Modifier.isStatic(method.flags())) {
|
||||||
|
// We don't support validating methods on static methods yet as it used to not be supported by CDI/Weld
|
||||||
|
// Supporting it will require some work in Hibernate Validator so we are going back to the old behavior of ignoring them but we log a warning.
|
||||||
|
LOGGER.warnf(
|
||||||
|
"Hibernate Validator does not support constraints on static methods yet. Constraints on %s are ignored.",
|
||||||
|
method.declaringClass().name().toString() + "#" + method.toString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isJaxrsMethod(method)) {
|
||||||
|
transformationContext.transform().add(DotName.createSimple(JaxrsEndPointValidated.class.getName())).done();
|
||||||
|
} else {
|
||||||
|
transformationContext.transform().add(DotName.createSimple(MethodValidated.class.getName())).done();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean requiresValidation(MethodInfo method) {
|
||||||
|
if (method.annotations().isEmpty()) {
|
||||||
|
// This method has no annotations of its own: look for inherited annotations
|
||||||
|
ClassInfo clazz = method.declaringClass();
|
||||||
|
String methodName = method.name();
|
||||||
|
for (Map.Entry<DotName, Set<String>> validatedMethod : inheritedAnnotationsToBeValidated.entrySet()) {
|
||||||
|
if (clazz.interfaceNames().contains(validatedMethod.getKey())
|
||||||
|
&& validatedMethod.getValue().contains(methodName)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (DotName consideredAnnotation : consideredAnnotations) {
|
||||||
|
if (method.hasAnnotation(consideredAnnotation)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isJaxrsMethod(MethodInfo method) {
|
||||||
|
for (DotName jaxrsMethodAnnotation : effectiveJaxRsMethodDefiningAnnotations) {
|
||||||
|
if (method.hasAnnotation(jaxrsMethodAnnotation)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
pattern.message=Value is not in line with the pattern
|
||||||
@ -0,0 +1 @@
|
|||||||
|
pattern.message=Non conforme
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
quarkus.locales=en,en-US,fr-FR
|
||||||
|
quarkus.default-locale=fr-FR
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>org.pagan.quarkus</groupId>
|
||||||
|
<artifactId>extensions</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>formalist-parent</artifactId>
|
||||||
|
<name>${project.artifactId}</name>
|
||||||
|
<packaging>pom</packaging>
|
||||||
|
|
||||||
|
<modules>
|
||||||
|
<module>deployment</module>
|
||||||
|
<module>runtime</module>
|
||||||
|
</modules>
|
||||||
|
|
||||||
|
</project>
|
||||||
@ -0,0 +1,122 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>org.pagan.quarkus</groupId>
|
||||||
|
<artifactId>formalist-parent</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>formalist</artifactId>
|
||||||
|
<name>${project.artifactId}</name>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.quarkus</groupId>
|
||||||
|
<artifactId>quarkus-core</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.quarkus</groupId>
|
||||||
|
<artifactId>quarkus-arc</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<!-- <dependency>
|
||||||
|
<groupId>io.quarkus</groupId>
|
||||||
|
<artifactId>quarkus-core-deployment</artifactId>
|
||||||
|
</dependency>-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.bval</groupId>
|
||||||
|
<artifactId>bval-jsr</artifactId>
|
||||||
|
<version>2.0.4</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>jakarta.validation</groupId>
|
||||||
|
<artifactId>jakarta.validation-api</artifactId>
|
||||||
|
<version>2.0.2</version>
|
||||||
|
<type>jar</type>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- EL implementation -->
|
||||||
|
<!-- <dependency>
|
||||||
|
<groupId>org.glassfish</groupId>
|
||||||
|
<artifactId>jakarta.el</artifactId>
|
||||||
|
</dependency>-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jboss.resteasy</groupId>
|
||||||
|
<artifactId>resteasy-core</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.apache.httpcomponents</groupId>
|
||||||
|
<artifactId>httpclient</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>commons-io</groupId>
|
||||||
|
<artifactId>commons-io</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>net.jcip</groupId>
|
||||||
|
<artifactId>jcip-annotations</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.reactivestreams</groupId>
|
||||||
|
<artifactId>reactive-streams</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.jboss.spec.javax.xml.bind</groupId>
|
||||||
|
<artifactId>jboss-jaxb-api_2.3_spec</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>javax.validation</groupId>
|
||||||
|
<artifactId>validation-api</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
<!-- <dependency>
|
||||||
|
<groupId>org.graalvm.nativeimage</groupId>
|
||||||
|
<artifactId>svm</artifactId>
|
||||||
|
</dependency>-->
|
||||||
|
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>io.quarkus</groupId>
|
||||||
|
<artifactId>quarkus-bootstrap-maven-plugin</artifactId>
|
||||||
|
<version>${quarkus.platform.version}</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<goals>
|
||||||
|
<goal>extension-descriptor</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<deployment>${project.groupId}:${project.artifactId}-deployment:${project.version}</deployment>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
<!-- <configuration>
|
||||||
|
<excludedArtifacts>
|
||||||
|
<excludedArtifact>javax.validation:validation-api</excludedArtifact>
|
||||||
|
</excludedArtifacts>
|
||||||
|
</configuration>-->
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>${compiler-plugin.version}</version>
|
||||||
|
<configuration>
|
||||||
|
<annotationProcessorPaths>
|
||||||
|
<path>
|
||||||
|
<groupId>io.quarkus</groupId>
|
||||||
|
<artifactId>quarkus-extension-processor</artifactId>
|
||||||
|
<version>${quarkus.platform.version}</version>
|
||||||
|
</path>
|
||||||
|
</annotationProcessorPaths>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
@ -0,0 +1,79 @@
|
|||||||
|
package org.pagan.formalist;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import javax.validation.ClockProvider;
|
||||||
|
import javax.validation.ConstraintValidatorFactory;
|
||||||
|
import javax.validation.MessageInterpolator;
|
||||||
|
import javax.validation.ParameterNameProvider;
|
||||||
|
import javax.validation.TraversableResolver;
|
||||||
|
import javax.validation.Validation;
|
||||||
|
import javax.validation.ValidatorFactory;
|
||||||
|
import javax.validation.valueextraction.ValueExtractor;
|
||||||
|
|
||||||
|
import io.quarkus.arc.Arc;
|
||||||
|
import io.quarkus.arc.InstanceHandle;
|
||||||
|
import io.quarkus.arc.runtime.BeanContainer;
|
||||||
|
import io.quarkus.arc.runtime.BeanContainerListener;
|
||||||
|
import io.quarkus.runtime.ShutdownContext;
|
||||||
|
import io.quarkus.runtime.annotations.Recorder;
|
||||||
|
import org.apache.bval.jsr.ApacheValidationProvider;
|
||||||
|
import org.apache.bval.jsr.ApacheValidatorConfiguration;
|
||||||
|
|
||||||
|
@Recorder
|
||||||
|
public class HibernateValidatorRecorder {
|
||||||
|
|
||||||
|
public BeanContainerListener initializeValidatorFactory(Set<Class<?>> classesToBeValidated,
|
||||||
|
Set<String> detectedBuiltinConstraints, boolean hasXmlConfiguration, ShutdownContext shutdownContext) {
|
||||||
|
return (BeanContainer container) -> {
|
||||||
|
ApacheValidatorConfiguration configuration = Validation.byProvider(ApacheValidationProvider.class)
|
||||||
|
.configure();
|
||||||
|
|
||||||
|
if (!hasXmlConfiguration) {
|
||||||
|
configuration.ignoreXmlConfiguration();
|
||||||
|
}
|
||||||
|
|
||||||
|
InstanceHandle<ConstraintValidatorFactory> configuredConstraintValidatorFactory = Arc.container()
|
||||||
|
.instance(ConstraintValidatorFactory.class);
|
||||||
|
configuration.constraintValidatorFactory(configuredConstraintValidatorFactory.get());
|
||||||
|
|
||||||
|
InstanceHandle<MessageInterpolator> configuredMessageInterpolator = Arc.container()
|
||||||
|
.instance(MessageInterpolator.class);
|
||||||
|
if (configuredMessageInterpolator.isAvailable()) {
|
||||||
|
configuration.messageInterpolator(configuredMessageInterpolator.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
InstanceHandle<TraversableResolver> configuredTraversableResolver = Arc.container()
|
||||||
|
.instance(TraversableResolver.class);
|
||||||
|
if (configuredTraversableResolver.isAvailable()) {
|
||||||
|
configuration.traversableResolver(configuredTraversableResolver.get());
|
||||||
|
} else {
|
||||||
|
configuration.traversableResolver(new TraverseAllTraversableResolver());
|
||||||
|
}
|
||||||
|
|
||||||
|
InstanceHandle<ParameterNameProvider> configuredParameterNameProvider = Arc.container()
|
||||||
|
.instance(ParameterNameProvider.class);
|
||||||
|
if (configuredParameterNameProvider.isAvailable()) {
|
||||||
|
configuration.parameterNameProvider(configuredParameterNameProvider.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
InstanceHandle<ClockProvider> configuredClockProvider = Arc.container().instance(ClockProvider.class);
|
||||||
|
if (configuredClockProvider.isAvailable()) {
|
||||||
|
configuration.clockProvider(configuredClockProvider.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Automatically add all the values extractors declared as beans
|
||||||
|
for (ValueExtractor<?> valueExtractor : Arc.container().beanManager().createInstance()
|
||||||
|
.select(ValueExtractor.class)) {
|
||||||
|
configuration.addValueExtractor(valueExtractor);
|
||||||
|
}
|
||||||
|
|
||||||
|
ValidatorFactory validatorFactory = configuration.buildValidatorFactory();
|
||||||
|
ValidatorHolder.initialize(validatorFactory);
|
||||||
|
|
||||||
|
// Close the ValidatorFactory on shutdown
|
||||||
|
shutdownContext.addShutdownTask(validatorFactory::close);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
package org.pagan.formalist;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
|
||||||
|
import javax.validation.Path;
|
||||||
|
import javax.validation.Path.Node;
|
||||||
|
import javax.validation.TraversableResolver;
|
||||||
|
|
||||||
|
class TraverseAllTraversableResolver implements TraversableResolver {
|
||||||
|
|
||||||
|
TraverseAllTraversableResolver() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isReachable(Object traversableObject, Node traversableProperty, Class<?> rootBeanType,
|
||||||
|
Path pathToTraversableObject, ElementType elementType) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCascadable(Object traversableObject, Node traversableProperty, Class<?> rootBeanType,
|
||||||
|
Path pathToTraversableObject, ElementType elementType) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
package org.pagan.formalist;
|
||||||
|
|
||||||
|
import javax.validation.Validator;
|
||||||
|
import javax.validation.ValidatorFactory;
|
||||||
|
|
||||||
|
public class ValidatorHolder {
|
||||||
|
|
||||||
|
private static ValidatorFactory validatorFactory;
|
||||||
|
|
||||||
|
private static Validator validator;
|
||||||
|
|
||||||
|
static void initialize(ValidatorFactory validatorFactory) {
|
||||||
|
ValidatorHolder.validatorFactory = validatorFactory;
|
||||||
|
ValidatorHolder.validator = validatorFactory.getValidator();
|
||||||
|
}
|
||||||
|
|
||||||
|
static ValidatorFactory getValidatorFactory() {
|
||||||
|
return validatorFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Validator getValidator() {
|
||||||
|
return validator;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
package org.pagan.formalist;
|
||||||
|
|
||||||
|
import javax.enterprise.context.ApplicationScoped;
|
||||||
|
import javax.enterprise.inject.Produces;
|
||||||
|
import javax.inject.Named;
|
||||||
|
import javax.validation.Validator;
|
||||||
|
import javax.validation.ValidatorFactory;
|
||||||
|
|
||||||
|
@ApplicationScoped
|
||||||
|
public class ValidatorProvider {
|
||||||
|
|
||||||
|
@Produces
|
||||||
|
@Named("quarkus-formalist-validator-factory")
|
||||||
|
public ValidatorFactory factory() {
|
||||||
|
return ValidatorHolder.getValidatorFactory();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Produces
|
||||||
|
public Validator validator() {
|
||||||
|
return ValidatorHolder.getValidator();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
package org.pagan.formalist.annotataions;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Inherited;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
import javax.interceptor.InterceptorBinding;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marker class to indicate a JAX-RS end point should be validated.
|
||||||
|
*/
|
||||||
|
@Inherited
|
||||||
|
@InterceptorBinding
|
||||||
|
@Retention(value = RetentionPolicy.RUNTIME)
|
||||||
|
@Target({ ElementType.TYPE, ElementType.METHOD })
|
||||||
|
public @interface JaxrsEndPointValidated {
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
package org.pagan.formalist.annotataions;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Inherited;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
import javax.interceptor.InterceptorBinding;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marker class to indicate a method should be validated.
|
||||||
|
*/
|
||||||
|
@Inherited
|
||||||
|
@InterceptorBinding
|
||||||
|
@Retention(value = RetentionPolicy.RUNTIME)
|
||||||
|
@Target({ ElementType.TYPE, ElementType.METHOD })
|
||||||
|
public @interface MethodValidated {
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,159 @@
|
|||||||
|
package org.pagan.formalist.interceptor;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.lang.reflect.Member;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.interceptor.InvocationContext;
|
||||||
|
import javax.validation.ConstraintViolation;
|
||||||
|
import javax.validation.ConstraintViolationException;
|
||||||
|
import javax.validation.ElementKind;
|
||||||
|
import javax.validation.Path;
|
||||||
|
import javax.validation.Validator;
|
||||||
|
import javax.validation.executable.ExecutableValidator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NOTE: this is a copy of the interceptor present in //. For now, I prefer not
|
||||||
|
* depending on this artifact but this might change in the future.
|
||||||
|
* <p>
|
||||||
|
* An interceptor which performs a validation of the Bean Validation constraints
|
||||||
|
* specified at the parameters and/or return values of intercepted methods using
|
||||||
|
* the method validation functionality provided by Hibernate Validator.
|
||||||
|
*
|
||||||
|
* @author Gunnar Morling
|
||||||
|
* @author Hardy Ferentschik
|
||||||
|
*/
|
||||||
|
public abstract class AbstractMethodValidationInterceptor implements Serializable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The validator to be used for method validation.
|
||||||
|
* <p>
|
||||||
|
* Although the concrete validator is not necessarily serializable (and HV's
|
||||||
|
* implementation indeed isn't) it is still alright to have it as non-transient
|
||||||
|
* field here. Upon passivation not the validator itself will be serialized, but
|
||||||
|
* the proxy injected here, which in turn is serializable.
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
@Inject
|
||||||
|
Validator validator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the Bean Validation constraints specified at the parameters and/or
|
||||||
|
* return value of the intercepted method.
|
||||||
|
*
|
||||||
|
* @param ctx The context of the intercepted method invocation.
|
||||||
|
* @param customValidator Custom Validator
|
||||||
|
*
|
||||||
|
* @return The result of the method invocation.
|
||||||
|
*
|
||||||
|
* @throws Exception Any exception caused by the intercepted method invocation.
|
||||||
|
* A {@link ConstraintViolationException} in case at least one
|
||||||
|
* constraint violation occurred either during parameter or
|
||||||
|
* return value validation.
|
||||||
|
*/
|
||||||
|
protected Object validateMethodInvocation(InvocationContext ctx) throws Exception {
|
||||||
|
|
||||||
|
ExecutableValidator executableValidator = validator.forExecutables();
|
||||||
|
Set<ConstraintViolation<Object>> violations = executableValidator.validateParameters(ctx.getTarget(),
|
||||||
|
ctx.getMethod(), ctx.getParameters());
|
||||||
|
|
||||||
|
if (!violations.isEmpty()) {
|
||||||
|
throw new ConstraintViolationException(getMessage(ctx.getMethod(), ctx.getParameters(), violations),
|
||||||
|
violations);
|
||||||
|
}
|
||||||
|
|
||||||
|
Object result = ctx.proceed();
|
||||||
|
|
||||||
|
violations = executableValidator.validateReturnValue(ctx.getTarget(), ctx.getMethod(), result);
|
||||||
|
|
||||||
|
if (!violations.isEmpty()) {
|
||||||
|
throw new ConstraintViolationException(getMessage(ctx.getMethod(), ctx.getParameters(), violations),
|
||||||
|
violations);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the Bean Validation constraints specified at the parameters and/or
|
||||||
|
* return value of the intercepted constructor.
|
||||||
|
*
|
||||||
|
* @param ctx The context of the intercepted constructor invocation.
|
||||||
|
*
|
||||||
|
* @throws Exception Any exception caused by the intercepted constructor
|
||||||
|
* invocation. A {@link ConstraintViolationException} in case
|
||||||
|
* at least one constraint violation occurred either during
|
||||||
|
* parameter or return value validation.
|
||||||
|
*/
|
||||||
|
protected void validateConstructorInvocation(InvocationContext ctx) throws Exception {
|
||||||
|
ExecutableValidator executableValidator = validator.forExecutables();
|
||||||
|
Set<? extends ConstraintViolation<?>> violations = executableValidator
|
||||||
|
.validateConstructorParameters(ctx.getConstructor(), ctx.getParameters());
|
||||||
|
|
||||||
|
if (!violations.isEmpty()) {
|
||||||
|
throw new ConstraintViolationException(getMessage(ctx.getConstructor(), ctx.getParameters(), violations),
|
||||||
|
violations);
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.proceed();
|
||||||
|
Object createdObject = ctx.getTarget();
|
||||||
|
|
||||||
|
violations = validator.forExecutables().validateConstructorReturnValue(ctx.getConstructor(), createdObject);
|
||||||
|
|
||||||
|
if (!violations.isEmpty()) {
|
||||||
|
throw new ConstraintViolationException(getMessage(ctx.getConstructor(), ctx.getParameters(), violations),
|
||||||
|
violations);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getMessage(Member member, Object[] args, Set<? extends ConstraintViolation<?>> violations) {
|
||||||
|
|
||||||
|
StringBuilder message = new StringBuilder();
|
||||||
|
message.append(violations.size());
|
||||||
|
message.append(" constraint violation(s) occurred during method validation.");
|
||||||
|
message.append("\nConstructor or Method: ");
|
||||||
|
message.append(member);
|
||||||
|
message.append("\nArgument values: ");
|
||||||
|
message.append(Arrays.toString(args));
|
||||||
|
message.append("\nConstraint violations: ");
|
||||||
|
|
||||||
|
int i = 1;
|
||||||
|
for (ConstraintViolation<?> constraintViolation : violations) {
|
||||||
|
Path.Node leafNode = getLeafNode(constraintViolation);
|
||||||
|
|
||||||
|
message.append("\n (");
|
||||||
|
message.append(i);
|
||||||
|
message.append(")");
|
||||||
|
message.append(" Kind: ");
|
||||||
|
message.append(leafNode.getKind());
|
||||||
|
if (leafNode.getKind() == ElementKind.PARAMETER) {
|
||||||
|
message.append("\n parameter index: ");
|
||||||
|
message.append(leafNode.as(Path.ParameterNode.class).getParameterIndex());
|
||||||
|
}
|
||||||
|
message.append("\n message: ");
|
||||||
|
message.append(constraintViolation.getMessage());
|
||||||
|
message.append("\n root bean: ");
|
||||||
|
message.append(constraintViolation.getRootBean());
|
||||||
|
message.append("\n property path: ");
|
||||||
|
message.append(constraintViolation.getPropertyPath());
|
||||||
|
message.append("\n constraint: ");
|
||||||
|
message.append(constraintViolation.getConstraintDescriptor().getAnnotation());
|
||||||
|
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return message.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Path.Node getLeafNode(ConstraintViolation<?> constraintViolation) {
|
||||||
|
Iterator<Path.Node> nodes = constraintViolation.getPropertyPath().iterator();
|
||||||
|
Path.Node leafNode = null;
|
||||||
|
while (nodes.hasNext()) {
|
||||||
|
leafNode = nodes.next();
|
||||||
|
}
|
||||||
|
return leafNode;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
package org.pagan.formalist.interceptor;
|
||||||
|
|
||||||
|
import org.pagan.formalist.annotataions.MethodValidated;
|
||||||
|
import javax.annotation.Priority;
|
||||||
|
import javax.interceptor.AroundConstruct;
|
||||||
|
import javax.interceptor.AroundInvoke;
|
||||||
|
import javax.interceptor.Interceptor;
|
||||||
|
import javax.interceptor.InvocationContext;
|
||||||
|
|
||||||
|
@MethodValidated
|
||||||
|
@Interceptor
|
||||||
|
@Priority(Interceptor.Priority.PLATFORM_AFTER + 800)
|
||||||
|
public class MethodValidationInterceptor extends AbstractMethodValidationInterceptor {
|
||||||
|
|
||||||
|
@AroundInvoke
|
||||||
|
@Override
|
||||||
|
public Object validateMethodInvocation(InvocationContext ctx) throws Exception {
|
||||||
|
return super.validateMethodInvocation(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AroundConstruct
|
||||||
|
@Override
|
||||||
|
public void validateConstructorInvocation(InvocationContext ctx) throws Exception {
|
||||||
|
super.validateConstructorInvocation(ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,50 @@
|
|||||||
|
package org.pagan.formalist.jaxrs;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
import javax.validation.ConstraintViolation;
|
||||||
|
import javax.validation.ElementKind;
|
||||||
|
import javax.validation.Path.Node;
|
||||||
|
|
||||||
|
import org.jboss.resteasy.api.validation.ConstraintType;
|
||||||
|
import org.jboss.resteasy.resteasy_jaxrs.i18n.Messages;
|
||||||
|
import org.jboss.resteasy.spi.validation.ConstraintTypeUtil;
|
||||||
|
|
||||||
|
public class ConstraintTypeUtil20 implements ConstraintTypeUtil {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ConstraintType.Type getConstraintType(Object o) {
|
||||||
|
if (!(o instanceof ConstraintViolation)) {
|
||||||
|
throw new RuntimeException(Messages.MESSAGES.unknownObjectPassedAsConstraintViolation(o));
|
||||||
|
}
|
||||||
|
ConstraintViolation<?> v = ConstraintViolation.class.cast(o);
|
||||||
|
|
||||||
|
Iterator<Node> nodes = v.getPropertyPath().iterator();
|
||||||
|
Node firstNode = nodes.next();
|
||||||
|
|
||||||
|
switch (firstNode.getKind()) {
|
||||||
|
case BEAN:
|
||||||
|
return ConstraintType.Type.CLASS;
|
||||||
|
case CONSTRUCTOR:
|
||||||
|
case METHOD:
|
||||||
|
Node secondNode = nodes.next();
|
||||||
|
|
||||||
|
if (secondNode.getKind() == ElementKind.PARAMETER
|
||||||
|
|| secondNode.getKind() == ElementKind.CROSS_PARAMETER) {
|
||||||
|
return ConstraintType.Type.PARAMETER;
|
||||||
|
} else if (secondNode.getKind() == ElementKind.RETURN_VALUE) {
|
||||||
|
return ConstraintType.Type.RETURN_VALUE;
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException(Messages.MESSAGES.unexpectedPathNodeViolation(secondNode.getKind()));
|
||||||
|
}
|
||||||
|
case PROPERTY:
|
||||||
|
return ConstraintType.Type.PROPERTY;
|
||||||
|
case CROSS_PARAMETER:
|
||||||
|
case PARAMETER:
|
||||||
|
case RETURN_VALUE:
|
||||||
|
case CONTAINER_ELEMENT: // we shouldn't encounter these element types at the root
|
||||||
|
default:
|
||||||
|
throw new RuntimeException(Messages.MESSAGES.unexpectedPathNode(firstNode.getKind()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,51 @@
|
|||||||
|
package org.pagan.formalist.jaxrs;
|
||||||
|
|
||||||
|
import org.pagan.formalist.annotataions.JaxrsEndPointValidated;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.annotation.Priority;
|
||||||
|
import javax.interceptor.AroundConstruct;
|
||||||
|
import javax.interceptor.AroundInvoke;
|
||||||
|
import javax.interceptor.Interceptor;
|
||||||
|
import javax.interceptor.InvocationContext;
|
||||||
|
import javax.validation.ConstraintViolationException;
|
||||||
|
import javax.ws.rs.core.MediaType;
|
||||||
|
|
||||||
|
import org.jboss.resteasy.util.MediaTypeHelper;
|
||||||
|
|
||||||
|
import org.pagan.formalist.interceptor.AbstractMethodValidationInterceptor;
|
||||||
|
|
||||||
|
@JaxrsEndPointValidated
|
||||||
|
@Interceptor
|
||||||
|
@Priority(Interceptor.Priority.PLATFORM_AFTER + 800)
|
||||||
|
public class JaxrsEndPointValidationInterceptor extends AbstractMethodValidationInterceptor {
|
||||||
|
|
||||||
|
@AroundInvoke
|
||||||
|
@Override
|
||||||
|
public Object validateMethodInvocation(InvocationContext ctx) throws Exception {
|
||||||
|
try {
|
||||||
|
return super.validateMethodInvocation(ctx);
|
||||||
|
} catch (ConstraintViolationException e) {
|
||||||
|
throw new ResteasyViolationExceptionImpl(e.getConstraintViolations(), getAccept(ctx.getMethod()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AroundConstruct
|
||||||
|
@Override
|
||||||
|
public void validateConstructorInvocation(InvocationContext ctx) throws Exception {
|
||||||
|
super.validateConstructorInvocation(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<MediaType> getAccept(Method method) {
|
||||||
|
MediaType[] producedMediaTypes = MediaTypeHelper.getProduces(method.getDeclaringClass(), method);
|
||||||
|
|
||||||
|
if (producedMediaTypes == null) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Arrays.asList(producedMediaTypes);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
package org.pagan.formalist.jaxrs;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import javax.validation.ConstraintViolation;
|
||||||
|
import javax.ws.rs.core.MediaType;
|
||||||
|
|
||||||
|
import org.jboss.resteasy.api.validation.ResteasyViolationException;
|
||||||
|
import org.jboss.resteasy.core.ResteasyContext;
|
||||||
|
import org.jboss.resteasy.spi.ResteasyConfiguration;
|
||||||
|
import org.jboss.resteasy.spi.validation.ConstraintTypeUtil;
|
||||||
|
|
||||||
|
public class ResteasyViolationExceptionImpl extends ResteasyViolationException {
|
||||||
|
private static final long serialVersionUID = 657697354453281559L;
|
||||||
|
|
||||||
|
public ResteasyViolationExceptionImpl(final Set<? extends ConstraintViolation<?>> constraintViolations,
|
||||||
|
final List<MediaType> accept) {
|
||||||
|
super(constraintViolations, accept);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ConstraintTypeUtil getConstraintTypeUtil() {
|
||||||
|
return new ConstraintTypeUtil20();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ResteasyConfiguration getResteasyConfiguration() {
|
||||||
|
return ResteasyContext.getContextData(ResteasyConfiguration.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,108 @@
|
|||||||
|
package org.pagan.formalist.jaxrs;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.validation.ConstraintDeclarationException;
|
||||||
|
import javax.validation.ConstraintDefinitionException;
|
||||||
|
import javax.validation.GroupDefinitionException;
|
||||||
|
import javax.validation.ValidationException;
|
||||||
|
import javax.ws.rs.core.MediaType;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import javax.ws.rs.core.Response.ResponseBuilder;
|
||||||
|
import javax.ws.rs.core.Response.Status;
|
||||||
|
import javax.ws.rs.ext.ExceptionMapper;
|
||||||
|
import javax.ws.rs.ext.Provider;
|
||||||
|
|
||||||
|
import org.jboss.resteasy.api.validation.ResteasyViolationException;
|
||||||
|
import org.jboss.resteasy.api.validation.Validation;
|
||||||
|
import org.jboss.resteasy.api.validation.ViolationReport;
|
||||||
|
|
||||||
|
@Provider
|
||||||
|
public class ResteasyViolationExceptionMapper implements ExceptionMapper<ValidationException> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response toResponse(ValidationException exception) {
|
||||||
|
|
||||||
|
if (exception instanceof ConstraintDefinitionException) {
|
||||||
|
return buildResponse(unwrapException(exception), MediaType.TEXT_PLAIN, Status.INTERNAL_SERVER_ERROR);
|
||||||
|
}
|
||||||
|
if (exception instanceof ConstraintDeclarationException) {
|
||||||
|
return buildResponse(unwrapException(exception), MediaType.TEXT_PLAIN, Status.INTERNAL_SERVER_ERROR);
|
||||||
|
}
|
||||||
|
if (exception instanceof GroupDefinitionException) {
|
||||||
|
return buildResponse(unwrapException(exception), MediaType.TEXT_PLAIN, Status.INTERNAL_SERVER_ERROR);
|
||||||
|
}
|
||||||
|
if (exception instanceof ResteasyViolationException) {
|
||||||
|
ResteasyViolationException resteasyViolationException = ResteasyViolationException.class.cast(exception);
|
||||||
|
Exception e = resteasyViolationException.getException();
|
||||||
|
if (e != null) {
|
||||||
|
return buildResponse(unwrapException(e), MediaType.TEXT_PLAIN, Status.INTERNAL_SERVER_ERROR);
|
||||||
|
} else if (resteasyViolationException.getReturnValueViolations().isEmpty()) {
|
||||||
|
return buildViolationReportResponse(resteasyViolationException, Status.BAD_REQUEST);
|
||||||
|
} else {
|
||||||
|
return buildViolationReportResponse(resteasyViolationException, Status.INTERNAL_SERVER_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buildResponse(unwrapException(exception), MediaType.TEXT_PLAIN, Status.INTERNAL_SERVER_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Response buildResponse(Object entity, String mediaType, Status status) {
|
||||||
|
ResponseBuilder builder = Response.status(status).entity(entity);
|
||||||
|
builder.type(MediaType.TEXT_PLAIN);
|
||||||
|
builder.header(Validation.VALIDATION_HEADER, "true");
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Response buildViolationReportResponse(ResteasyViolationException exception, Status status) {
|
||||||
|
ResponseBuilder builder = Response.status(status);
|
||||||
|
builder.header(Validation.VALIDATION_HEADER, "true");
|
||||||
|
|
||||||
|
// Check standard media types.
|
||||||
|
MediaType mediaType = getAcceptMediaType(exception.getAccept());
|
||||||
|
if (mediaType != null) {
|
||||||
|
builder.type(mediaType);
|
||||||
|
builder.entity(new ViolationReport(exception));
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default media type.
|
||||||
|
builder.type(MediaType.TEXT_PLAIN);
|
||||||
|
builder.entity(exception.toString());
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String unwrapException(Throwable t) {
|
||||||
|
StringBuffer sb = new StringBuffer();
|
||||||
|
doUnwrapException(sb, t);
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doUnwrapException(StringBuffer sb, Throwable t) {
|
||||||
|
if (t == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sb.append(t.toString());
|
||||||
|
if (t.getCause() != null && t != t.getCause()) {
|
||||||
|
sb.append('[');
|
||||||
|
doUnwrapException(sb, t.getCause());
|
||||||
|
sb.append(']');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private MediaType getAcceptMediaType(List<MediaType> accept) {
|
||||||
|
Iterator<MediaType> it = accept.iterator();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
MediaType mt = it.next();
|
||||||
|
if (MediaType.APPLICATION_XML_TYPE.getType().equals(mt.getType())
|
||||||
|
&& MediaType.APPLICATION_XML_TYPE.getSubtype().equals(mt.getSubtype())) {
|
||||||
|
return MediaType.APPLICATION_XML_TYPE;
|
||||||
|
}
|
||||||
|
if (MediaType.APPLICATION_JSON_TYPE.getType().equals(mt.getType())
|
||||||
|
&& MediaType.APPLICATION_JSON_TYPE.getSubtype().equals(mt.getSubtype())) {
|
||||||
|
return MediaType.APPLICATION_JSON_TYPE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
---
|
||||||
|
name: "Formalist (Apache Bval) Validator"
|
||||||
|
metadata:
|
||||||
|
short-name: "bean validation"
|
||||||
|
keywords:
|
||||||
|
- "formalist-validator"
|
||||||
|
- "bean-validation"
|
||||||
|
- "validation"
|
||||||
|
categories:
|
||||||
|
- "web"
|
||||||
|
- "data"
|
||||||
|
status: "stable"
|
||||||
@ -0,0 +1 @@
|
|||||||
|
org.pagan.formalist.jaxrs.ResteasyViolationExceptionMapper
|
||||||
Loading…
Reference in New Issue