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