diff --git a/formalist/deployment/pom.xml b/formalist/deployment/pom.xml new file mode 100644 index 0000000..bec93a4 --- /dev/null +++ b/formalist/deployment/pom.xml @@ -0,0 +1,82 @@ + + + + 4.0.0 + + + org.pagan.quarkus + janitor-parent + 1.0-SNAPSHOT + + + formalist-deployment + + + io.quarkus + quarkus-core-deployment + + + io.quarkus + quarkus-arc-deployment + + + io.quarkus + quarkus-resteasy-server-common-spi + + + jakarta.validation + jakarta.validation-api + 2.0.2 + jar + + + + + org.pagan.quarkus + formalist + 1.0-SNAPSHOT + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${compiler-plugin.version} + + + + io.quarkus + quarkus-extension-processor + ${quarkus.platform.version} + + + + + + + + diff --git a/formalist/deployment/src/main/java/org/pagan/formalist/HibernateValidatorProcessor.java b/formalist/deployment/src/main/java/org/pagan/formalist/HibernateValidatorProcessor.java new file mode 100644 index 0000000..b44ecac --- /dev/null +++ b/formalist/deployment/src/main/java/org/pagan/formalist/HibernateValidatorProcessor.java @@ -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 additionalBeans, + BuildProducer 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() { + @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 reflectiveFields, + BuildProducer reflectiveMethods, + BuildProducer annotationsTransformers, + BeanArchiveIndexBuildItem beanArchiveIndexBuildItem, CombinedIndexBuildItem combinedIndexBuildItem, + BuildProducer feature, + BuildProducer beanContainerListener, + ShutdownContextBuildItem shutdownContext, + List 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 consideredAnnotations = new HashSet<>(); + + // Set 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 classNamesToBeValidated = new HashSet<>(); + Map> inheritedAnnotationsToBeValidated = new HashMap<>(); + Set detectedBuiltinConstraints = new HashSet<>(); + + for (DotName consideredAnnotation : consideredAnnotations) { + Collection 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 additionalJaxRsMethodAnnotationsDotNames = new HashSet<>( + additionalJaxRsResourceMethodAnnotations.size()); + for (AdditionalJaxRsResourceMethodAnnotationsBuildItem additionalJaxRsResourceMethodAnnotation : additionalJaxRsResourceMethodAnnotations) { + additionalJaxRsMethodAnnotationsDotNames + .addAll(additionalJaxRsResourceMethodAnnotation.getAnnotationClasses()); + } + annotationsTransformers.produce(new AnnotationsTransformerBuildItem(new MethodValidatedAnnotationsTransformer( + consideredAnnotations, additionalJaxRsMethodAnnotationsDotNames, inheritedAnnotationsToBeValidated))); + + Set> 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 builtinConstraints, + Set 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 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 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> 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()) + .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; + } +} diff --git a/formalist/deployment/src/main/java/org/pagan/formalist/MethodValidatedAnnotationsTransformer.java b/formalist/deployment/src/main/java/org/pagan/formalist/MethodValidatedAnnotationsTransformer.java new file mode 100644 index 0000000..dd0e20e --- /dev/null +++ b/formalist/deployment/src/main/java/org/pagan/formalist/MethodValidatedAnnotationsTransformer.java @@ -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 consideredAnnotations; + private final Collection effectiveJaxRsMethodDefiningAnnotations; + private final Map> inheritedAnnotationsToBeValidated; + + MethodValidatedAnnotationsTransformer(Set consideredAnnotations, + Collection additionalJaxRsMethodAnnotationsDotNames, + Map> 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> 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; + } +} diff --git a/formalist/deployment/src/test/resources/ValidationMessages.properties b/formalist/deployment/src/test/resources/ValidationMessages.properties new file mode 100644 index 0000000..48a3bbf --- /dev/null +++ b/formalist/deployment/src/test/resources/ValidationMessages.properties @@ -0,0 +1 @@ +pattern.message=Value is not in line with the pattern diff --git a/formalist/deployment/src/test/resources/ValidationMessages_fr_FR.properties b/formalist/deployment/src/test/resources/ValidationMessages_fr_FR.properties new file mode 100644 index 0000000..38a6544 --- /dev/null +++ b/formalist/deployment/src/test/resources/ValidationMessages_fr_FR.properties @@ -0,0 +1 @@ +pattern.message=Non conforme diff --git a/formalist/deployment/src/test/resources/application.properties b/formalist/deployment/src/test/resources/application.properties new file mode 100644 index 0000000..a096d3c --- /dev/null +++ b/formalist/deployment/src/test/resources/application.properties @@ -0,0 +1,2 @@ +quarkus.locales=en,en-US,fr-FR +quarkus.default-locale=fr-FR \ No newline at end of file diff --git a/formalist/pom.xml b/formalist/pom.xml new file mode 100644 index 0000000..9667fe0 --- /dev/null +++ b/formalist/pom.xml @@ -0,0 +1,22 @@ + + + 4.0.0 + + + org.pagan.quarkus + extensions + 1.0-SNAPSHOT + + + formalist-parent + ${project.artifactId} + pom + + + deployment + runtime + + + \ No newline at end of file diff --git a/formalist/runtime/pom.xml b/formalist/runtime/pom.xml new file mode 100644 index 0000000..6aa1a1b --- /dev/null +++ b/formalist/runtime/pom.xml @@ -0,0 +1,122 @@ + + + 4.0.0 + + + org.pagan.quarkus + formalist-parent + 1.0-SNAPSHOT + + + formalist + ${project.artifactId} + + + io.quarkus + quarkus-core + + + io.quarkus + quarkus-arc + + + + org.apache.bval + bval-jsr + 2.0.4 + + + jakarta.validation + jakarta.validation-api + 2.0.2 + jar + + + + + + + org.jboss.resteasy + resteasy-core + true + + + org.apache.httpcomponents + httpclient + + + commons-io + commons-io + + + net.jcip + jcip-annotations + + + org.reactivestreams + reactive-streams + + + org.jboss.spec.javax.xml.bind + jboss-jaxb-api_2.3_spec + + + javax.validation + validation-api + + + + + + + + + + + io.quarkus + quarkus-bootstrap-maven-plugin + ${quarkus.platform.version} + + + + extension-descriptor + + + ${project.groupId}:${project.artifactId}-deployment:${project.version} + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${compiler-plugin.version} + + + + io.quarkus + quarkus-extension-processor + ${quarkus.platform.version} + + + + + + + diff --git a/formalist/runtime/src/main/java/org/pagan/formalist/HibernateValidatorRecorder.java b/formalist/runtime/src/main/java/org/pagan/formalist/HibernateValidatorRecorder.java new file mode 100644 index 0000000..5b6202a --- /dev/null +++ b/formalist/runtime/src/main/java/org/pagan/formalist/HibernateValidatorRecorder.java @@ -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> classesToBeValidated, + Set detectedBuiltinConstraints, boolean hasXmlConfiguration, ShutdownContext shutdownContext) { + return (BeanContainer container) -> { + ApacheValidatorConfiguration configuration = Validation.byProvider(ApacheValidationProvider.class) + .configure(); + + if (!hasXmlConfiguration) { + configuration.ignoreXmlConfiguration(); + } + + InstanceHandle configuredConstraintValidatorFactory = Arc.container() + .instance(ConstraintValidatorFactory.class); + configuration.constraintValidatorFactory(configuredConstraintValidatorFactory.get()); + + InstanceHandle configuredMessageInterpolator = Arc.container() + .instance(MessageInterpolator.class); + if (configuredMessageInterpolator.isAvailable()) { + configuration.messageInterpolator(configuredMessageInterpolator.get()); + } + + InstanceHandle configuredTraversableResolver = Arc.container() + .instance(TraversableResolver.class); + if (configuredTraversableResolver.isAvailable()) { + configuration.traversableResolver(configuredTraversableResolver.get()); + } else { + configuration.traversableResolver(new TraverseAllTraversableResolver()); + } + + InstanceHandle configuredParameterNameProvider = Arc.container() + .instance(ParameterNameProvider.class); + if (configuredParameterNameProvider.isAvailable()) { + configuration.parameterNameProvider(configuredParameterNameProvider.get()); + } + + InstanceHandle 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); + }; + } + +} diff --git a/formalist/runtime/src/main/java/org/pagan/formalist/TraverseAllTraversableResolver.java b/formalist/runtime/src/main/java/org/pagan/formalist/TraverseAllTraversableResolver.java new file mode 100644 index 0000000..a716dbf --- /dev/null +++ b/formalist/runtime/src/main/java/org/pagan/formalist/TraverseAllTraversableResolver.java @@ -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; + } +} diff --git a/formalist/runtime/src/main/java/org/pagan/formalist/ValidatorHolder.java b/formalist/runtime/src/main/java/org/pagan/formalist/ValidatorHolder.java new file mode 100644 index 0000000..795588c --- /dev/null +++ b/formalist/runtime/src/main/java/org/pagan/formalist/ValidatorHolder.java @@ -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; + } +} diff --git a/formalist/runtime/src/main/java/org/pagan/formalist/ValidatorProvider.java b/formalist/runtime/src/main/java/org/pagan/formalist/ValidatorProvider.java new file mode 100644 index 0000000..e6c5cf7 --- /dev/null +++ b/formalist/runtime/src/main/java/org/pagan/formalist/ValidatorProvider.java @@ -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(); + } +} diff --git a/formalist/runtime/src/main/java/org/pagan/formalist/annotataions/JaxrsEndPointValidated.java b/formalist/runtime/src/main/java/org/pagan/formalist/annotataions/JaxrsEndPointValidated.java new file mode 100644 index 0000000..7e81d2b --- /dev/null +++ b/formalist/runtime/src/main/java/org/pagan/formalist/annotataions/JaxrsEndPointValidated.java @@ -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 { + +} diff --git a/formalist/runtime/src/main/java/org/pagan/formalist/annotataions/MethodValidated.java b/formalist/runtime/src/main/java/org/pagan/formalist/annotataions/MethodValidated.java new file mode 100644 index 0000000..3366fac --- /dev/null +++ b/formalist/runtime/src/main/java/org/pagan/formalist/annotataions/MethodValidated.java @@ -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 { + +} diff --git a/formalist/runtime/src/main/java/org/pagan/formalist/interceptor/AbstractMethodValidationInterceptor.java b/formalist/runtime/src/main/java/org/pagan/formalist/interceptor/AbstractMethodValidationInterceptor.java new file mode 100644 index 0000000..ba844c4 --- /dev/null +++ b/formalist/runtime/src/main/java/org/pagan/formalist/interceptor/AbstractMethodValidationInterceptor.java @@ -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. + *

+ * 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. + *

+ * 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. + *

+ */ + @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> 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> 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> 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 nodes = constraintViolation.getPropertyPath().iterator(); + Path.Node leafNode = null; + while (nodes.hasNext()) { + leafNode = nodes.next(); + } + return leafNode; + } +} diff --git a/formalist/runtime/src/main/java/org/pagan/formalist/interceptor/MethodValidationInterceptor.java b/formalist/runtime/src/main/java/org/pagan/formalist/interceptor/MethodValidationInterceptor.java new file mode 100644 index 0000000..cc9355c --- /dev/null +++ b/formalist/runtime/src/main/java/org/pagan/formalist/interceptor/MethodValidationInterceptor.java @@ -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); + } +} diff --git a/formalist/runtime/src/main/java/org/pagan/formalist/jaxrs/ConstraintTypeUtil20.java b/formalist/runtime/src/main/java/org/pagan/formalist/jaxrs/ConstraintTypeUtil20.java new file mode 100644 index 0000000..ac28970 --- /dev/null +++ b/formalist/runtime/src/main/java/org/pagan/formalist/jaxrs/ConstraintTypeUtil20.java @@ -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 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())); + } + } +} diff --git a/formalist/runtime/src/main/java/org/pagan/formalist/jaxrs/JaxrsEndPointValidationInterceptor.java b/formalist/runtime/src/main/java/org/pagan/formalist/jaxrs/JaxrsEndPointValidationInterceptor.java new file mode 100644 index 0000000..eb8607d --- /dev/null +++ b/formalist/runtime/src/main/java/org/pagan/formalist/jaxrs/JaxrsEndPointValidationInterceptor.java @@ -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 getAccept(Method method) { + MediaType[] producedMediaTypes = MediaTypeHelper.getProduces(method.getDeclaringClass(), method); + + if (producedMediaTypes == null) { + return Collections.emptyList(); + } + + return Arrays.asList(producedMediaTypes); + } +} diff --git a/formalist/runtime/src/main/java/org/pagan/formalist/jaxrs/ResteasyViolationExceptionImpl.java b/formalist/runtime/src/main/java/org/pagan/formalist/jaxrs/ResteasyViolationExceptionImpl.java new file mode 100644 index 0000000..4c1d838 --- /dev/null +++ b/formalist/runtime/src/main/java/org/pagan/formalist/jaxrs/ResteasyViolationExceptionImpl.java @@ -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> constraintViolations, + final List accept) { + super(constraintViolations, accept); + } + + @Override + public ConstraintTypeUtil getConstraintTypeUtil() { + return new ConstraintTypeUtil20(); + } + + @Override + protected ResteasyConfiguration getResteasyConfiguration() { + return ResteasyContext.getContextData(ResteasyConfiguration.class); + } +} diff --git a/formalist/runtime/src/main/java/org/pagan/formalist/jaxrs/ResteasyViolationExceptionMapper.java b/formalist/runtime/src/main/java/org/pagan/formalist/jaxrs/ResteasyViolationExceptionMapper.java new file mode 100644 index 0000000..c6c8b74 --- /dev/null +++ b/formalist/runtime/src/main/java/org/pagan/formalist/jaxrs/ResteasyViolationExceptionMapper.java @@ -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 { + + @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 accept) { + Iterator 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; + } +} diff --git a/formalist/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/formalist/runtime/src/main/resources/META-INF/quarkus-extension.yaml new file mode 100644 index 0000000..62c4a2e --- /dev/null +++ b/formalist/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -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" diff --git a/formalist/runtime/src/main/resources/META-INF/services/javax.ws.rs.ext.Providers b/formalist/runtime/src/main/resources/META-INF/services/javax.ws.rs.ext.Providers new file mode 100644 index 0000000..5a3e9a3 --- /dev/null +++ b/formalist/runtime/src/main/resources/META-INF/services/javax.ws.rs.ext.Providers @@ -0,0 +1 @@ +org.pagan.formalist.jaxrs.ResteasyViolationExceptionMapper diff --git a/janitor/runtime/pom.xml b/janitor/runtime/pom.xml index 4b2c7d9..88e53b6 100644 --- a/janitor/runtime/pom.xml +++ b/janitor/runtime/pom.xml @@ -32,7 +32,6 @@ quarkus-security ${quarkus.platform.version} - demo