| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.bval.jsr.xml; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.lang.annotation.Annotation; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Map; |
| import java.util.Optional; |
| import java.util.Set; |
| import java.util.function.BiConsumer; |
| import java.util.function.Consumer; |
| import java.util.stream.Collectors; |
| |
| import javax.validation.ValidationException; |
| import javax.validation.ValidatorFactory; |
| import javax.validation.spi.ConfigurationState; |
| |
| import org.apache.bval.jsr.ApacheValidatorFactory; |
| import org.apache.bval.jsr.metadata.MetadataBuilder; |
| import org.apache.bval.jsr.metadata.MetadataBuilder.ForBean; |
| import org.apache.bval.jsr.metadata.MetadataSource; |
| import org.apache.bval.jsr.metadata.ValidatorMappingProvider; |
| import org.apache.bval.jsr.metadata.XmlBuilder; |
| import org.apache.bval.jsr.metadata.XmlValidationMappingProvider; |
| import org.apache.bval.util.Exceptions; |
| import org.apache.bval.util.Validate; |
| import org.apache.bval.util.reflection.Reflection; |
| import org.apache.commons.weaver.privilizer.Privilizing; |
| import org.apache.commons.weaver.privilizer.Privilizing.CallTo; |
| import org.xml.sax.InputSource; |
| |
| /** |
| * Uses JAXB to parse constraints.xml based on the validation-mapping XML schema. |
| */ |
| @Privilizing(@CallTo(Reflection.class)) |
| public class ValidationMappingParser implements MetadataSource { |
| private static final SchemaManager SCHEMA_MANAGER = new SchemaManager.Builder() |
| .add(XmlBuilder.Version.v10.getId(), "http://jboss.org/xml/ns/javax/validation/mapping", |
| "META-INF/validation-mapping-1.0.xsd") |
| .add(XmlBuilder.Version.v11.getId(), "http://jboss.org/xml/ns/javax/validation/mapping", |
| "META-INF/validation-mapping-1.1.xsd") |
| .add(XmlBuilder.Version.v30.getId(), "https://jakarta.ee/xml/ns/validation/mapping", |
| "META-INF/validation-mapping-3.0.xsd") |
| .add(XmlBuilder.Version.v20.getId(), "http://xmlns.jcp.org/xml/ns/validation/mapping", |
| "META-INF/validation-mapping-2.0.xsd") |
| .build(); |
| |
| private ApacheValidatorFactory validatorFactory; |
| |
| @Override |
| public void initialize(ApacheValidatorFactory validatorFactory) { |
| this.validatorFactory = Validate.notNull(validatorFactory); |
| } |
| |
| @Override |
| public void process(ConfigurationState configurationState, |
| Consumer<ValidatorMappingProvider> addValidatorMappingProvider, BiConsumer<Class<?>, ForBean<?>> addBuilder) { |
| Validate.validState(validatorFactory != null, "validatorFactory unknown"); |
| |
| if (configurationState.isIgnoreXmlConfiguration()) { |
| return; |
| } |
| final Set<Class<?>> beanTypes = new HashSet<>(); |
| for (final InputStream xmlStream : configurationState.getMappingStreams()) { |
| final ConstraintMappingsType mapping = parseXmlMappings(xmlStream); |
| |
| Optional.of(mapping).map(this::toMappingProvider).ifPresent(addValidatorMappingProvider); |
| |
| final Map<Class<?>, MetadataBuilder.ForBean<?>> builders = |
| new XmlBuilder(validatorFactory, mapping).forBeans(); |
| if (Collections.disjoint(beanTypes, builders.keySet())) { |
| builders.forEach(addBuilder::accept); |
| beanTypes.addAll(builders.keySet()); |
| } else { |
| Exceptions.raise(ValidationException::new, |
| builders.keySet().stream().filter(beanTypes::contains).map(Class::getName).collect(Collectors |
| .joining("bean classes specified multiple times for XML validation mapping: [", "; ", "]"))); |
| } |
| } |
| } |
| |
| /** |
| * @param in |
| * XML stream to parse using the validation-mapping-1.0.xsd |
| */ |
| private ConstraintMappingsType parseXmlMappings(final InputStream in) { |
| try { |
| return SCHEMA_MANAGER.unmarshal(new InputSource(in), ConstraintMappingsType.class); |
| } catch (Exception e) { |
| throw new ValidationException("Failed to parse XML deployment descriptor file.", e); |
| } finally { |
| try { |
| in.reset(); // can be read several times + we ensured it was |
| // re-readable in addMapping() |
| } catch (final IOException e) { |
| // no-op |
| } |
| } |
| } |
| |
| private ValidatorMappingProvider toMappingProvider(ConstraintMappingsType mapping) { |
| if (mapping.getConstraintDefinition().isEmpty()) { |
| return null; |
| } |
| final Map<Class<? extends Annotation>, ValidatedByType> validatorMappings = new HashMap<>(); |
| |
| for (ConstraintDefinitionType constraintDefinition : mapping.getConstraintDefinition()) { |
| final String annotationClassName = constraintDefinition.getAnnotation(); |
| |
| final Class<?> clazz = loadClass(annotationClassName, mapping.getDefaultPackage()); |
| |
| Exceptions.raiseUnless(clazz.isAnnotation(), ValidationException::new, "%s is not an annotation", |
| annotationClassName); |
| |
| final Class<? extends Annotation> annotationClass = clazz.asSubclass(Annotation.class); |
| |
| Exceptions.raiseIf(validatorMappings.containsKey(annotationClass), ValidationException::new, |
| "XML constraint validator(s) for %s already configured.", annotationClass); |
| |
| validatorMappings.put(annotationClass, constraintDefinition.getValidatedBy()); |
| } |
| return new XmlValidationMappingProvider(validatorMappings, |
| cn -> toQualifiedClassName(cn, mapping.getDefaultPackage())); |
| } |
| |
| private Class<?> loadClass(String className, String defaultPackage) { |
| final String fqn = toQualifiedClassName(className, defaultPackage); |
| try { |
| return Reflection.toClass(fqn, Reflection.loaderFromThreadOrClass(ValidationMappingParser.class)); |
| } catch (ClassNotFoundException ex) { |
| throw Exceptions.create(ValidationException::new, ex, "Unable to load class: %s", fqn); |
| } |
| } |
| |
| private String toQualifiedClassName(String className, String defaultPackage) { |
| if (!isQualifiedClass(className)) { |
| if (className.startsWith("[L") && className.endsWith(";")) { |
| className = "[L" + defaultPackage + '.' + className.substring(2); |
| } else { |
| className = defaultPackage + '.' + className; |
| } |
| } |
| return className; |
| } |
| |
| private boolean isQualifiedClass(String clazz) { |
| return clazz.indexOf('.') >= 0; |
| } |
| } |