| /* |
| * Copyright (c) 2016, 2020 Oracle and/or its affiliates. All rights reserved. |
| * |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License v. 2.0 which is available at |
| * http://www.eclipse.org/legal/epl-2.0, |
| * or the Eclipse Distribution License v. 1.0 which is available at |
| * http://www.eclipse.org/org/documents/edl-v10.php. |
| * |
| * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause |
| */ |
| |
| // Contributors: |
| // Marcel Valovy - initial API and implementation |
| // Dmitry Kornilov - BeanValidationHelper refactoring |
| // Miroslav Kos - BeanValidationHelper refactoring |
| package org.eclipse.persistence.jaxb; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.concurrent.Callable; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| |
| import javax.xml.parsers.ParserConfigurationException; |
| import javax.xml.parsers.SAXParser; |
| import javax.xml.parsers.SAXParserFactory; |
| |
| import org.eclipse.persistence.exceptions.BeanValidationException; |
| import org.eclipse.persistence.internal.helper.XMLHelper; |
| import org.eclipse.persistence.internal.security.PrivilegedAccessHelper; |
| import org.xml.sax.Attributes; |
| import org.xml.sax.SAXException; |
| import org.xml.sax.helpers.DefaultHandler; |
| |
| /** |
| * Detects external Bean Validation configuration. |
| * <p> |
| * Strategy:<br> |
| * 1. Parse validation.xml, looking for a constraints-file reference.<br> |
| * 2. For each reference, if file is found, parses the constraints file and puts all classes declared under |
| * {@literal <bean class="clazz">} into {@code constraintsOnClasses} field |
| * of {@link org.eclipse.persistence.jaxb.BeanValidationHelper} class |
| * with {@link Boolean#TRUE} value. |
| * <p> |
| * This class contains resources-burdening instance fields (e.g. SAXParser) and as such was designed to be instantiated |
| * once (make the instance BOUNDED) and have {@link #call()} method called on that instance once. |
| * <p> |
| * Not suitable for singleton (memory burden). The method #parse() will be invoked only once per class load of this |
| * class. After that the instance and all its fields should be made collectible by GC. |
| * |
| * @author Marcel Valovy |
| * @author Dmitry Kornilov |
| * @author Miroslav Kos |
| * @since 2.6 |
| */ |
| public class ValidationXMLReader implements Callable<Map<Class<?>, Boolean>> { |
| |
| public static final String DEFAULT_PACKAGE_QNAME = "default-package"; |
| public static final String BEAN_QNAME = "bean"; |
| public static final String CONSTRAINT_MAPPING_QNAME = "constraint-mapping"; |
| public static final String CLASS_QNAME = "class"; |
| public static final String PACKAGE_SEPARATOR = "."; |
| |
| private static final String VALIDATION_XML = "META-INF/validation.xml"; |
| private static final Logger LOGGER = Logger.getLogger(ValidationXMLReader.class.getName()); |
| |
| private final List<String> constraintsFiles = new ArrayList<>(2); |
| |
| private final Map<Class<?>, Boolean> constraintsOnClasses = new HashMap<>(); |
| |
| // Created lazily |
| private SAXParser saxParser; |
| |
| /** |
| * Parses validation.xml. |
| * @return returns a map with classes found in validation.xml as keys and true as a value. Never returns null. |
| */ |
| @Override |
| public Map<Class<?>, Boolean> call() throws Exception { |
| parseValidationXML(VALIDATION_XML, validationHandler); |
| |
| if (!constraintsFiles.isEmpty()) { |
| parseConstraintFiles(); |
| } |
| return constraintsOnClasses; |
| } |
| |
| /** |
| * Checks if validation.xml exists. |
| */ |
| public static boolean isValidationXmlPresent() { |
| return getThreadContextClassLoader().getResource(VALIDATION_XML) != null; |
| } |
| |
| private void parseConstraintFiles() { |
| final class ConstrainedClassesDetector extends DefaultHandler { |
| |
| private boolean defaultPackageElement = false; |
| private String defaultPackage = ""; |
| |
| @Override |
| public void startElement(String uri, String localName, String qName, |
| Attributes attributes) throws SAXException { |
| |
| if (DEFAULT_PACKAGE_QNAME.equalsIgnoreCase(qName)) { |
| defaultPackageElement = true; |
| } else if (BEAN_QNAME.equalsIgnoreCase(qName)) { |
| String className = defaultPackage + PACKAGE_SEPARATOR + attributes.getValue(CLASS_QNAME); |
| if (LOGGER.isLoggable(Level.INFO)) { |
| String msg = "Detected external constraints on class " + className; |
| LOGGER.info(msg); |
| } |
| try { |
| Class<?> clazz = ReflectionUtils.forName(className); |
| constraintsOnClasses.put(clazz, Boolean.TRUE); |
| } catch (ClassNotFoundException e) { |
| String errMsg = "Loading found class failed. Exception: " + e.getMessage(); |
| LOGGER.warning(errMsg); |
| } |
| } |
| } |
| |
| @Override |
| public void characters(char[] ch, int start, int length) throws SAXException { |
| if (defaultPackageElement) { |
| defaultPackage = new String(ch, start, length); |
| defaultPackageElement = false; |
| } |
| } |
| } |
| |
| // Parse constraints file referenced in validation.xml. Add all classes declared under <bean class="clazz"> to |
| // org.eclipse.persistence.jaxb.BeanValidationHelper#constraintsOnClasses with value Boolean#TRUE. |
| for (String file : constraintsFiles) { |
| parseValidationXML(file, new ConstrainedClassesDetector()); |
| } |
| } |
| |
| /** |
| * Lazy getter for SAX parser. |
| */ |
| private SAXParser getSaxParser() { |
| if (saxParser == null) { |
| try { |
| SAXParserFactory factory = XMLHelper.createParserFactory(false); |
| saxParser = factory.newSAXParser(); |
| } catch (ParserConfigurationException | SAXException e) { |
| String msg = "ValidationXMLReader initialization failed. Exception: " + e.getMessage(); |
| LOGGER.severe(msg); |
| throw new BeanValidationException(msg, e); |
| } |
| } |
| return saxParser; |
| } |
| |
| private void parseValidationXML(String constraintsFilePath, DefaultHandler handler) { |
| try (InputStream validationXml = getThreadContextClassLoader().getResourceAsStream(constraintsFilePath)) { |
| if (validationXml != null) { |
| getSaxParser().parse(validationXml, handler); |
| } |
| } catch (SAXException | IOException ex) { |
| LOGGER.log(Level.WARNING, "Parsing of validation.xml failed.", ex); |
| } |
| } |
| |
| private static ClassLoader getThreadContextClassLoader() { |
| return PrivilegedAccessHelper.callDoPrivileged( |
| () -> Thread.currentThread().getContextClassLoader() |
| ); |
| } |
| |
| private final DefaultHandler validationHandler = new DefaultHandler() { |
| |
| private boolean constraintsFileElement = false; |
| |
| @Override |
| public void startElement(String uri, String localName, String qName, Attributes attributes) { |
| |
| if (CONSTRAINT_MAPPING_QNAME.equalsIgnoreCase(qName)) { |
| constraintsFileElement = true; |
| } |
| } |
| |
| @Override |
| public void characters(char[] ch, int start, int length) { |
| |
| if (constraintsFileElement) { |
| constraintsFiles.add(new String(ch, start, length)); |
| constraintsFileElement = false; |
| } |
| } |
| }; |
| |
| } |