Bug #500376 - Fix MOXy bean validation performance - 2.6 back port
Signed-off-by: Petros Splinakis <petros.splinakis@oracle.com>
Reviewed-by: Dmitry Kornilov <dmitry.kornilov@oracle.com>
diff --git a/jpa/org.eclipse.persistence.jpa/META-INF/MANIFEST.MF b/jpa/org.eclipse.persistence.jpa/META-INF/MANIFEST.MF
index 9811be2..aee8aa5 100644
--- a/jpa/org.eclipse.persistence.jpa/META-INF/MANIFEST.MF
+++ b/jpa/org.eclipse.persistence.jpa/META-INF/MANIFEST.MF
@@ -55,7 +55,6 @@
javax.transaction.xa;version="1.1.0";resolution:=optional,
javax.validation;resolution:=optional,
javax.validation.groups;resolution:=optional,
- javax.validation.metadata;resolution:=optional,
javax.xml.parsers;resolution:=optional,
javax.xml.transform;resolution:=optional,
javax.xml.transform.stream;resolution:=optional,
diff --git a/jpa/org.eclipse.persistence.jpa/src/org/eclipse/persistence/internal/jpa/metadata/listeners/BeanValidationListener.java b/jpa/org.eclipse.persistence.jpa/src/org/eclipse/persistence/internal/jpa/metadata/listeners/BeanValidationListener.java
index 78fc7a1..6c6e8de 100644
--- a/jpa/org.eclipse.persistence.jpa/src/org/eclipse/persistence/internal/jpa/metadata/listeners/BeanValidationListener.java
+++ b/jpa/org.eclipse.persistence.jpa/src/org/eclipse/persistence/internal/jpa/metadata/listeners/BeanValidationListener.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2009, 2016 Sun Microsystems, Inc, IBM Corporation. All rights reserved.
+ * Copyright (c) 2009, 2015 Sun Microsystems, Inc, IBM Corporation. All rights reserved.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0
* which accompanies this distribution.
@@ -10,8 +10,6 @@
* Contributors:
* 08/20/2014-2.5 Rick Curtis
* - 441890: Cache Validator instances.
- * 02/17/2016-2.6 Dalia Abo Sheasha
- * - 487889: Fix EclipseLink Bean Validation optimization
* Marcel Valovy - 2.6 - skip validation of objects that are not constrained.
******************************************************************************/
@@ -100,11 +98,10 @@
private void validateOnCallbackEvent(DescriptorEvent event, String callbackEventName, Class[] validationGroup) {
Object source = event.getSource();
- Validator validator = getValidator(event);
boolean noOptimization = "true".equalsIgnoreCase((String) event.getSession().getProperty(PersistenceUnitProperties.BEAN_VALIDATION_NO_OPTIMISATION));
- boolean shouldValidate = noOptimization || validator.getConstraintsForClass(source.getClass()).isBeanConstrained();
+ boolean shouldValidate = noOptimization || beanValidationHelper.isConstrained(source.getClass());
if (shouldValidate) {
- Set<ConstraintViolation<Object>> constraintViolations = validator.validate(source, validationGroup);
+ Set<ConstraintViolation<Object>> constraintViolations = getValidator(event).validate(source, validationGroup);
if (constraintViolations.size() > 0) {
// There were errors while call to validate above.
// Throw a ConstrainViolationException as required by the spec.
diff --git a/moxy/eclipselink.moxy.test/src/org/eclipse/persistence/testing/jaxb/beanvalidation/BeanValidationHelperTestCase.java b/moxy/eclipselink.moxy.test/src/org/eclipse/persistence/testing/jaxb/beanvalidation/BeanValidationHelperTestCase.java
new file mode 100644
index 0000000..ce3a968
--- /dev/null
+++ b/moxy/eclipselink.moxy.test/src/org/eclipse/persistence/testing/jaxb/beanvalidation/BeanValidationHelperTestCase.java
@@ -0,0 +1,159 @@
+/*******************************************************************************
+ * Copyright (c) 2015 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 v1.0 and Eclipse Distribution License v. 1.0
+ * which accompanies this distribution.
+ * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ * Dmitry Kornilov - initial implementation
+ ******************************************************************************/
+package org.eclipse.persistence.testing.jaxb.beanvalidation;
+
+import mockit.Expectations;
+import mockit.Mock;
+import mockit.MockUp;
+import mockit.Mocked;
+import mockit.integration.junit4.JMockit;
+import org.eclipse.persistence.jaxb.BeanValidationHelper;
+import org.eclipse.persistence.jaxb.ValidationXMLReader;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * This class contains BeanValidationHelper related tests.
+ *
+ * @author Dmitry Kornilov
+ */
+@RunWith(JMockit.class)
+public class BeanValidationHelperTestCase {
+
+ /**
+ * Tests that validation.xml parsing is not called if validation.xml doesn't exist.
+ */
+ @Test
+ public void testValidationXmlExists(final @Mocked ValidationXMLReader reader) throws NamingException {
+ new Expectations() {{
+ ValidationXMLReader.isValidationXmlPresent(); result = false;
+ new ValidationXMLReader(); times = 0;
+ }};
+
+ BeanValidationHelper beanValidationHelper = new BeanValidationHelper();
+ assertNotNull(beanValidationHelper.getConstraintsMap());
+ assertTrue(beanValidationHelper.getConstraintsMap().size() == 0);
+ }
+
+ /**
+ * Tests that managed executor service doesn't get shutdown.
+ */
+ @Test
+ public void testManagedExecutorService(final @Mocked InitialContext initialContext,
+ final @Mocked ExecutorService managedExecutorService,
+ final @Mocked ValidationXMLReader reader) throws NamingException {
+ new Expectations() {{
+ ValidationXMLReader.isValidationXmlPresent(); result = true;
+ new InitialContext();
+ initialContext.lookup("java:comp/env/concurrent/ThreadPool"); returns(managedExecutorService);
+ new ValidationXMLReader();
+ managedExecutorService.submit((Callable) any);
+ managedExecutorService.shutdown(); times=0;
+ }};
+
+ new BeanValidationHelper();
+ }
+
+ /**
+ * Tests that JDK executor service gets properly shutdown.
+ */
+ @Test
+ public void testJDKExecutorService(final @Mocked InitialContext initialContext,
+ final @Mocked ExecutorService jdkExecutorService,
+ final @Mocked ValidationXMLReader reader) throws NamingException {
+ new MockUp<Executors>() {
+ @Mock
+ public ExecutorService newFixedThreadPool(int nThreads) {
+ return jdkExecutorService;
+ }
+ };
+
+ new Expectations() {
+ {
+ ValidationXMLReader.isValidationXmlPresent(); result = true;
+ new InitialContext();
+ initialContext.lookup("java:comp/env/concurrent/ThreadPool"); result = new NamingException();
+ new ValidationXMLReader();
+ jdkExecutorService.submit((Callable) any);
+ jdkExecutorService.shutdown();
+ }
+ };
+
+ new BeanValidationHelper();
+ }
+
+ /**
+ * Tests that validation.xml gets parsed when asynchronous attempt failed.
+ */
+ @Test
+ public void testAsyncParsingFailed(final @Mocked ValidationXMLReader reader) throws Exception {
+ new MockUp<Future<Map<Class<?>, Boolean>>>() {
+ @Mock
+ public Future<Map<Class<?>, Boolean>> get() throws InterruptedException, ExecutionException {
+ throw new InterruptedException();
+ }
+ };
+
+ new Expectations() {{
+ ValidationXMLReader.isValidationXmlPresent(); result = true;
+ new ValidationXMLReader();
+ reader.call();
+ }};
+
+ BeanValidationHelper beanValidationHelper = new BeanValidationHelper();
+ assertNotNull(beanValidationHelper.getConstraintsMap());
+ }
+
+ /**
+ * Tests that validation.xml gets parsed if async task submission failed.
+ */
+ @Test
+ public void testAsyncSubmissionFailed(final @Mocked InitialContext initialContext,
+ final @Mocked ExecutorService jdkExecutorService,
+ final @Mocked ValidationXMLReader reader) throws Exception {
+ new MockUp<Executors>() {
+ @Mock
+ public ExecutorService newFixedThreadPool(int nThreads) {
+ return jdkExecutorService;
+ }
+ };
+
+ new Expectations() {
+ {
+ ValidationXMLReader.isValidationXmlPresent(); result = true;
+ new InitialContext();
+ initialContext.lookup("java:comp/env/concurrent/ThreadPool"); result = new NamingException();
+ new ValidationXMLReader();
+ jdkExecutorService.submit((Callable) any); result = new OutOfMemoryError();
+ jdkExecutorService.shutdown();
+ new ValidationXMLReader();
+ reader.call();
+ }
+ };
+
+ BeanValidationHelper beanValidationHelper = new BeanValidationHelper();
+ assertNotNull(beanValidationHelper.getConstraintsMap());
+ }
+}
diff --git a/moxy/eclipselink.moxy.test/src/org/eclipse/persistence/testing/jaxb/beanvalidation/BeanValidationSpecialtiesTestCase.java b/moxy/eclipselink.moxy.test/src/org/eclipse/persistence/testing/jaxb/beanvalidation/BeanValidationSpecialtiesTestCase.java
index eabb6ea..04e1508 100644
--- a/moxy/eclipselink.moxy.test/src/org/eclipse/persistence/testing/jaxb/beanvalidation/BeanValidationSpecialtiesTestCase.java
+++ b/moxy/eclipselink.moxy.test/src/org/eclipse/persistence/testing/jaxb/beanvalidation/BeanValidationSpecialtiesTestCase.java
@@ -32,6 +32,7 @@
import javax.validation.metadata.ConstraintDescriptor;
import org.eclipse.persistence.exceptions.BeanValidationException;
+import org.eclipse.persistence.jaxb.BeanValidationHelper;
import org.eclipse.persistence.jaxb.ConstraintViolationWrapper;
import org.eclipse.persistence.jaxb.JAXBContext;
import org.eclipse.persistence.jaxb.JAXBContextFactory;
@@ -103,36 +104,36 @@
}
}
-// /**
-// * Tests that we do not skip validation on classes that do not have any bean validation annotations on fields or
-// * methods but have some on constructors.
-// */
-// public void testConstructorAnnotations() throws Exception {
-// JAXBContext context = (JAXBContext)JAXBContextFactory.createContext(new Class[]{ConstructorAnnotatedEmployee.class}, null);
-// JAXBMarshaller marshaller = context.createMarshaller();
+ /**
+ * Tests that we do not skip validation on classes that do not have any bean validation annotations on fields or
+ * methods but have some on constructors.
+ */
+ public void testConstructorAnnotations() throws Exception {
+ JAXBContext context = (JAXBContext)JAXBContextFactory.createContext(new Class[]{ConstructorAnnotatedEmployee.class}, null);
+ JAXBMarshaller marshaller = context.createMarshaller();
+
+ ConstructorAnnotatedEmployee employee = new ConstructorAnnotatedEmployee(null);
+
+ try {
+ marshaller.marshal(employee, new StringWriter());
+ } catch (BeanValidationException ignored) {
+ }
+
+ // Ok, HV is not picking up constraints on constructor. But that does not mean anything. Our job is to ensure
+ // that we correctly identify that the class is constrained and pass the object to the underlying BV impl.
+ BeanValidationHelper beanValidationHelper = context.getBeanValidationHelper();
+ assertTrue(beanValidationHelper.getConstraintsMap().containsKey(ConstructorAnnotatedEmployee.class));
+
+ // This will not detect the constraints violation on constructor (on HV 5.1), although it should.
+// Set<? extends ConstraintViolation<?>> violations = marshaller.getConstraintViolations();
//
-// ConstructorAnnotatedEmployee employee = new ConstructorAnnotatedEmployee(null);
+// assertFalse(violations.isEmpty());
//
-// try {
-// marshaller.marshal(employee, new StringWriter());
-// } catch (BeanValidationException ignored) {
+// // For all, i.e. one constraintViolations.
+// for (ConstraintViolation constraintViolation : violations) {
+// assertEquals(NOT_NULL_MESSAGE, constraintViolation.getMessageTemplate());
// }
-//
-// // Ok, HV is not picking up constraints on constructor. But that does not mean anything. Our job is to ensure
-// // that we correctly identify that the class is constrained and pass the object to the underlying BV impl.
-// BeanValidationHelper beanValidationHelper = context.getBeanValidationHelper();
-// assertTrue(beanValidationHelper.getConstraintsMap().containsKey(ConstructorAnnotatedEmployee.class));
-//
-// // This will not detect the constraints violation on constructor (on HV 5.1), although it should.
-//// Set<? extends ConstraintViolation<?>> violations = marshaller.getConstraintViolations();
-////
-//// assertFalse(violations.isEmpty());
-////
-//// // For all, i.e. one constraintViolations.
-//// for (ConstraintViolation constraintViolation : violations) {
-//// assertEquals(NOT_NULL_MESSAGE, constraintViolation.getMessageTemplate());
-//// }
-// }
+ }
/**
* Tests {@link org.eclipse.persistence.jaxb.JAXBContextProperties#BEAN_VALIDATION_NO_OPTIMISATION} property.
diff --git a/moxy/eclipselink.moxy.test/src/org/eclipse/persistence/testing/jaxb/beanvalidation/ValidationXMLTestCase.java b/moxy/eclipselink.moxy.test/src/org/eclipse/persistence/testing/jaxb/beanvalidation/ValidationXMLTestCase.java
index 4901aaf..bd061ec 100644
--- a/moxy/eclipselink.moxy.test/src/org/eclipse/persistence/testing/jaxb/beanvalidation/ValidationXMLTestCase.java
+++ b/moxy/eclipselink.moxy.test/src/org/eclipse/persistence/testing/jaxb/beanvalidation/ValidationXMLTestCase.java
@@ -13,8 +13,8 @@
package org.eclipse.persistence.testing.jaxb.beanvalidation;
import org.eclipse.persistence.exceptions.BeanValidationException;
-import org.eclipse.persistence.internal.cache.AdvancedProcessor;
import org.eclipse.persistence.jaxb.ConstraintViolationWrapper;
+import org.eclipse.persistence.jaxb.JAXBContext;
import org.eclipse.persistence.jaxb.JAXBContextFactory;
import org.eclipse.persistence.jaxb.JAXBMarshaller;
import org.eclipse.persistence.testing.jaxb.beanvalidation.special.ExternallyConstrainedEmployee;
@@ -26,6 +26,7 @@
import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
+import java.lang.reflect.Field;
import java.net.URISyntaxException;
import java.util.Set;
@@ -108,6 +109,21 @@
@Before
public void setUp() throws Exception {
createTimeWindow();
+ resetBeanValidation();
+ }
+
+ private void resetBeanValidation() throws Exception {
+ Field beanValidationHelper = JAXBContext.class.getDeclaredField("beanValidationHelper");
+ Field beanValidationPresent = JAXBContext.class.getDeclaredField("beanValidationPresent");
+
+ beanValidationHelper.setAccessible(true);
+ beanValidationPresent.setAccessible(true);
+
+ beanValidationHelper.set(JAXBContext.class, null);
+ beanValidationPresent.set(JAXBContext.class, null);
+
+ beanValidationHelper.setAccessible(false);
+ beanValidationPresent.setAccessible(false);
}
@After
diff --git a/moxy/org.eclipse.persistence.moxy/src/org/eclipse/persistence/jaxb/BeanValidationHelper.java b/moxy/org.eclipse.persistence.moxy/src/org/eclipse/persistence/jaxb/BeanValidationHelper.java
new file mode 100644
index 0000000..2049fb1
--- /dev/null
+++ b/moxy/org.eclipse.persistence.moxy/src/org/eclipse/persistence/jaxb/BeanValidationHelper.java
@@ -0,0 +1,275 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 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 v1.0 and Eclipse Distribution License v. 1.0
+ * which accompanies this distribution.
+ * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ * Marcel Valovy - 2.6 - initial API and implementation
+ * Dmitry Kornilov - 2.6.1 - removed static maps
+ ******************************************************************************/
+package org.eclipse.persistence.jaxb;
+
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
+import javax.validation.Constraint;
+import javax.validation.Valid;
+import javax.validation.constraints.AssertFalse;
+import javax.validation.constraints.AssertTrue;
+import javax.validation.constraints.DecimalMax;
+import javax.validation.constraints.DecimalMin;
+import javax.validation.constraints.Digits;
+import javax.validation.constraints.Max;
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Past;
+import javax.validation.constraints.Pattern;
+import javax.validation.constraints.Size;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+import org.eclipse.persistence.logging.AbstractSessionLog;
+import org.eclipse.persistence.logging.SessionLog;
+/**
+ * INTERNAL:
+ *
+ * Asynchronously starts validation.xml file processing when created. Holds a map of classes with BV annotations.
+ *
+ * @author Marcel Valovy, Dmitry Kornilov
+ * @since 2.6
+ */
+final public class BeanValidationHelper {
+
+ private Future<Map<Class<?>, Boolean>> future;
+
+ /**
+ * Set of all default BeanValidation field or method annotations and known custom field or method constraints.
+ */
+ private final Set<Class<? extends Annotation>> knownConstraints = new HashSet<>();
+
+ /**
+ * Map of all classes that have undergone check for bean validation constraints.
+ * Maps the key with boolean value telling whether the class contains an annotation from {@link #knownConstraints}.
+ */
+ private Map<Class<?>, Boolean> constraintsOnClasses = null;
+
+ {
+ knownConstraints.add(Valid.class);
+ knownConstraints.add(Max.class);
+ knownConstraints.add(Min.class);
+ knownConstraints.add(DecimalMax.class);
+ knownConstraints.add(DecimalMin.class);
+ knownConstraints.add(Digits.class);
+ knownConstraints.add(NotNull.class);
+ knownConstraints.add(Pattern.class);
+ knownConstraints.add(Size.class);
+ knownConstraints.add(AssertTrue.class);
+ knownConstraints.add(AssertFalse.class);
+ knownConstraints.add(javax.validation.constraints.Future.class);
+ knownConstraints.add(Past.class);
+ knownConstraints.add(Max.List.class);
+ knownConstraints.add(Min.List.class);
+ knownConstraints.add(DecimalMax.List.class);
+ knownConstraints.add(DecimalMin.List.class);
+ knownConstraints.add(Digits.List.class);
+ knownConstraints.add(NotNull.List.class);
+ knownConstraints.add(Pattern.List.class);
+ knownConstraints.add(Size.List.class);
+ knownConstraints.add(AssertTrue.List.class);
+ knownConstraints.add(AssertFalse.List.class);
+ knownConstraints.add(javax.validation.constraints.Future.List.class);
+ knownConstraints.add(Past.List.class);
+ }
+
+ /**
+ * Creates a new instance. Starts asynchronous parsing of validation.xml if it exists.
+ */
+ public BeanValidationHelper() {
+ // Try to run validation.xml parsing asynchronously if validation.xml exists
+ if (ValidationXMLReader.isValidationXmlPresent()) {
+ parseValidationXmlAsync();
+ } else {
+ // validation.xml doesn't exist -> create an empty map
+ constraintsOnClasses = new HashMap<>();
+ }
+ }
+
+ /**
+ * Tells whether any of the class's fields, methods or constructors are constrained by Bean Validation annotations
+ * or custom constraints.
+ *
+ * @param clazz checked class
+ * @return true or false
+ */
+ boolean isConstrained(Class<?> clazz) {
+ Boolean annotated = getConstraintsMap().get(clazz);
+ if (annotated == null) {
+ annotated = detectConstraints(clazz);
+ getConstraintsMap().put(clazz, annotated);
+ }
+ return annotated;
+ }
+
+ /**
+ * Lazy getter for constraintsOnClasses property. Waits until the map is returned by async XML reader.
+ */
+ public Map<Class<?>, Boolean> getConstraintsMap() {
+ if (constraintsOnClasses == null) {
+ if (future == null) {
+ // This happens when submission of async task is failed. Run validation.xml parsing synchronously.
+ constraintsOnClasses = parseValidationXml();
+ } else {
+ // Async task was successfully submitted. get a result from future
+ try {
+ constraintsOnClasses = future.get();
+ } catch (InterruptedException | ExecutionException e) {
+ // For some reason the async parsing attempt failed. Call it synchronously.
+ AbstractSessionLog.getLog().log(SessionLog.WARNING, SessionLog.MOXY, "Error parsing validation.xml the async way", new Object[0], false);
+ AbstractSessionLog.getLog().logThrowable(SessionLog.WARNING, SessionLog.MOXY, e);
+ constraintsOnClasses = parseValidationXml();
+ }
+ }
+ }
+ return constraintsOnClasses;
+ }
+
+ /**
+ * Tries to run validation.xml parsing asynchronously.
+ */
+ private void parseValidationXmlAsync() {
+ Executor executor = null;
+ try {
+ executor = createExecutor();
+ future = executor.executorService.submit(new ValidationXMLReader());
+ } catch (Throwable e) {
+ // In the rare cases submitting a task throws OutOfMemoryError. In this case we call validation.xml
+ // parsing lazily when requested
+ AbstractSessionLog.getLog().log(SessionLog.WARNING, SessionLog.MOXY, "Error creating/submitting async validation.xml parsing task.", new Object[0], false);
+ AbstractSessionLog.getLog().logThrowable(SessionLog.WARNING, SessionLog.MOXY, e);
+ future = null;
+ } finally {
+ // Shutdown is needed only for JDK executor
+ if (executor != null && executor.shutdownNeeded) {
+ executor.executorService.shutdown();
+ }
+ }
+ }
+
+ /**
+ * Runs validation.xml parsing synchronously.
+ */
+ private Map<Class<?>, Boolean> parseValidationXml() {
+ final ValidationXMLReader reader = new ValidationXMLReader();
+ Map<Class<?>, Boolean> result;
+ try {
+ result = reader.call();
+ } catch (Exception e) {
+ AbstractSessionLog.getLog().log(SessionLog.WARNING, SessionLog.MOXY, "Error parsing validation.xml synchronously", new Object[0], false);
+ AbstractSessionLog.getLog().logThrowable(SessionLog.WARNING, SessionLog.MOXY, e);
+ result = new HashMap<>();
+ }
+ return result;
+ }
+
+ /**
+ * Reveals whether any of the class's fields or methods are constrained by Bean Validation annotations or custom
+ * constraints.
+ * Uses reflection.
+ */
+ private Boolean detectConstraints(Class<?> clazz) {
+ for (Field f : ReflectionUtils.getDeclaredFields(clazz)) {
+ for (Annotation a : f.getDeclaredAnnotations()) {
+ final Class<? extends Annotation> type = a.annotationType();
+ if (knownConstraints.contains(type)){
+ return true;
+ }
+ // Check for custom annotations on the field (+ check inheritance on class annotations).
+ // Custom bean validation annotation is defined by having @Constraint annotation on its class.
+ for (Annotation typesClassAnnotation : type.getAnnotations()) {
+ final Class<? extends Annotation> classAnnotationType = typesClassAnnotation.annotationType();
+ if (Constraint.class == classAnnotationType) {
+ knownConstraints.add(type);
+ return true;
+ }
+ }
+ }
+ }
+ for (Method m : ReflectionUtils.getDeclaredMethods(clazz)) {
+ for (Annotation a : m.getDeclaredAnnotations()) {
+ final Class<? extends Annotation> type = a.annotationType();
+ if (knownConstraints.contains(type)){
+ return true;
+ }
+ // Check for custom annotations on the method (+ check inheritance on class annotations).
+ // Custom bean validation annotation is defined by having @Constraint annotation on its class.
+ for (Annotation typesClassAnnotation : type.getAnnotations()) {
+ final Class<? extends Annotation> classAnnotationType = typesClassAnnotation.annotationType();
+ if (Constraint.class == classAnnotationType) {
+ knownConstraints.add(type);
+ return true;
+ }
+ }
+ }
+ }
+ for (Constructor<?> c : ReflectionUtils.getDeclaredConstructors(clazz)) {
+ for (Annotation a : c.getDeclaredAnnotations()) {
+ final Class<? extends Annotation> type = a.annotationType();
+ if (knownConstraints.contains(type)){
+ return true;
+ }
+ // Check for custom annotations on the constructor (+ check inheritance on class annotations).
+ // Custom bean validation annotation is defined by having @Constraint annotation on its class.
+ for (Annotation typesClassAnnotation : type.getAnnotations()) {
+ final Class<? extends Annotation> classAnnotationType = typesClassAnnotation.annotationType();
+ if (Constraint.class == classAnnotationType) {
+ knownConstraints.add(type);
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Creates an executor service. Tries to get a managed executor service. If failed creates a JDK one.
+ * Sets shutdownNeeded property to true in case JDK executor is created.
+ */
+ private Executor createExecutor() {
+ try {
+ InitialContext jndiCtx = new InitialContext();
+ // type: javax.enterprise.concurrent.ManagedExecutorService
+ // jndi-name: concurrent/ThreadPool
+ return new Executor((ExecutorService) jndiCtx.lookup("java:comp/env/concurrent/ThreadPool"), false);
+ } catch (NamingException ignored) {
+ // aka continue to proceed with retrieving jdk executor
+ }
+ return new Executor(Executors.newFixedThreadPool(1), true);
+ }
+
+ /**
+ * This class holds a managed or JDK executor instance with a flag indicating do we need to shut it down or not.
+ */
+ private static class Executor {
+ ExecutorService executorService;
+ boolean shutdownNeeded;
+
+ Executor(ExecutorService executorService, boolean shutdownNeeded) {
+ this.executorService = executorService;
+ this.shutdownNeeded = shutdownNeeded;
+ }
+ }
+}
diff --git a/moxy/org.eclipse.persistence.moxy/src/org/eclipse/persistence/jaxb/JAXBBeanValidator.java b/moxy/org.eclipse.persistence.moxy/src/org/eclipse/persistence/jaxb/JAXBBeanValidator.java
index 237e665..a0531c0 100644
--- a/moxy/org.eclipse.persistence.moxy/src/org/eclipse/persistence/jaxb/JAXBBeanValidator.java
+++ b/moxy/org.eclipse.persistence.moxy/src/org/eclipse/persistence/jaxb/JAXBBeanValidator.java
@@ -9,8 +9,6 @@
*
* Contributors:
* Marcel Valovy - 2.6 - initial implementation
- * 02/17/2016-2.6 Dalia Abo Sheasha
- * - 487889: Fix EclipseLink Bean Validation optimization
******************************************************************************/
package org.eclipse.persistence.jaxb;
@@ -115,8 +113,6 @@
*/
private boolean stopSearchingForValidator;
- private static boolean buildingFactory;
-
/**
* This field will usually be {@code null}. However, user may pass his own instance of
* {@link javax.validation.ValidatorFactory} to
@@ -173,7 +169,7 @@
/**
* PUBLIC:
*
- * First, check if validation has been turned off before.
+ * First, if validation has not been turned off before, check if passed value is constrained.
*
* Second, depending on Bean Validation Mode, either returns false or tries to initialize Validator:
* - AUTO tries to initialize Validator:
@@ -185,8 +181,7 @@
* BeanValidationMode is fetched from (un)marshaller upon each call.
* If change in mode is detected, the internal state of the JAXBBeanValidator will be switched.
*
- * Third, checks if passed value has any constraints using the Validator. If there are no constraints,
- * returns false since there is no need to validate.
+ * Third, analyses the value and determines whether validation may be skipped.
*
* @param beanValidationMode Bean validation mode - allowed values AUTO, CALLBACK, NONE.
* @param value validated object. It is passed because validation on some objects may be skipped,
@@ -209,19 +204,17 @@
this.noOptimisation = noOptimisation;
+ if (!isConstrainedObject(value)) return false;
+
/* Mode or validator factory was changed externally (or it's the first time this method is called). */
- if (!buildingFactory) {
- if (this.beanValidationMode != beanValidationMode || this.validatorFactory != preferredValidatorFactory) {
- this.beanValidationMode = beanValidationMode;
- this.validatorFactory = (ValidatorFactory) preferredValidatorFactory;
- changeInternalState();
- }
+ if (this.beanValidationMode != beanValidationMode || this.validatorFactory != preferredValidatorFactory) {
+ this.beanValidationMode = beanValidationMode;
+ this.validatorFactory = (ValidatorFactory)preferredValidatorFactory;
+ changeInternalState();
}
- /* If the changeInternalState() call detects that the beanValidationMode is NONE or it fails
- * to initialize the Validator, we will return false.
- * Otherwise, we will use the Validator to check if there are constraints on the value. */
- return canValidate && isConstrainedObject(value);
+ /* Is Validation implementation ready to validate. */
+ return canValidate;
}
/**
@@ -257,12 +250,9 @@
return !(value instanceof XmlBindings);
}
- if (validator == null) {
- return false;
- } else {
- /* Ensure that the class has constraints. If not, skip validation & speed things up. */
- return validator.getConstraintsForClass(value.getClass()).isBeanConstrained();
- }
+ /* Ensure that the class contains BV annotations. If not, skip validation & speed things up.
+ * note: This also effectively skips XmlBindings. */
+ return context.getBeanValidationHelper().isConstrained(value.getClass());
}
/**
@@ -345,9 +335,7 @@
private boolean initValidator() throws BeanValidationException {
if (validator == null && !stopSearchingForValidator){
try {
- buildingFactory = true;
ValidatorFactory factory = getValidatorFactory();
- buildingFactory = false;
validator = factory.getValidator();
printValidatorInfo();
} catch (ValidationException ve) {
diff --git a/moxy/org.eclipse.persistence.moxy/src/org/eclipse/persistence/jaxb/JAXBContext.java b/moxy/org.eclipse.persistence.moxy/src/org/eclipse/persistence/jaxb/JAXBContext.java
index eed7648..6cee732 100644
--- a/moxy/org.eclipse.persistence.moxy/src/org/eclipse/persistence/jaxb/JAXBContext.java
+++ b/moxy/org.eclipse.persistence.moxy/src/org/eclipse/persistence/jaxb/JAXBContext.java
@@ -11,8 +11,6 @@
* Oracle - initial API and implementation from Oracle TopLink
* Marcel Valovy - 2.6 - added case insensitive unmarshalling property
* Dmitry Kornilov - 2.6.1 - BeanValidationHelper refactoring
- * 02/17/2016-2.6 Dalia Abo Sheasha
- * - 487889: Fix EclipseLink Bean Validation optimization
******************************************************************************/
package org.eclipse.persistence.jaxb;
@@ -181,14 +179,19 @@
private boolean initializedXMLInputFactory = false;
private JAXBMarshaller jsonSchemaMarshaller;
+ private static volatile BeanValidationHelper beanValidationHelper;
+ private static volatile Boolean beanValidationPresent;
+
protected JAXBContext() {
super();
contextState = new JAXBContextState();
+ initBeanValidation();
}
protected JAXBContext(JAXBContextInput contextInput) throws javax.xml.bind.JAXBException {
this.contextInput = contextInput;
this.contextState = contextInput.createContextState();
+ initBeanValidation();
}
/**
@@ -197,6 +200,7 @@
*/
public JAXBContext(XMLContext context) {
contextState = new JAXBContextState(context);
+ initBeanValidation();
}
/**
@@ -205,6 +209,7 @@
*/
public JAXBContext(XMLContext context, Generator generator, Type[] boundTypes) {
contextState = new JAXBContextState(context, generator, boundTypes, null);
+ initBeanValidation();
}
/**
@@ -213,6 +218,31 @@
*/
public JAXBContext(XMLContext context, Generator generator, TypeMappingInfo[] boundTypes) {
contextState = new JAXBContextState(context, generator, boundTypes, null);
+ initBeanValidation();
+ }
+
+ /**
+ * Initializes bean validation if javax.validation.api bundle is on the class path.
+ */
+ private void initBeanValidation() {
+ if (beanValidationPresent == null) {
+ beanValidationPresent = BeanValidationChecker.isBeanValidationPresent();
+ }
+ if (beanValidationPresent && beanValidationHelper == null) {
+ synchronized (JAXBContext.class) {
+ if (beanValidationHelper == null) {
+ // Bean validation is optional
+ beanValidationHelper = new BeanValidationHelper();
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns BeanValidationHelper. Can return null if bean validation jar is not on class path.
+ */
+ public BeanValidationHelper getBeanValidationHelper() {
+ return beanValidationHelper;
}
public XMLInputFactory getXMLInputFactory() {
diff --git a/moxy/org.eclipse.persistence.moxy/src/org/eclipse/persistence/jaxb/ReflectionUtils.java b/moxy/org.eclipse.persistence.moxy/src/org/eclipse/persistence/jaxb/ReflectionUtils.java
new file mode 100644
index 0000000..e5c349a
--- /dev/null
+++ b/moxy/org.eclipse.persistence.moxy/src/org/eclipse/persistence/jaxb/ReflectionUtils.java
@@ -0,0 +1,393 @@
+/*******************************************************************************
+ * Copyright (c) 2014 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 v1.0 and Eclipse Distribution License v. 1.0
+ * which accompanies this distribution.
+ * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ * Marcel Valovy - 2.6 - initial implementation
+ ******************************************************************************/
+package org.eclipse.persistence.jaxb;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.security.PrivilegedAction;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+
+import static java.security.AccessController.doPrivileged;
+import static org.eclipse.persistence.internal.security.PrivilegedAccessHelper.shouldUsePrivilegedAccess;
+
+/**
+ * Utility class for handling reflection calls and using caller sensitive actions.
+ *
+ * - Singleton lazy-loaded actions honoring Initialization On Demand Holder idiom.
+ * - Lazy-loaded inner classes and inner interfaces. Only loaded if security is enabled.
+ *
+ * @author Marcel Valovy - marcel.valovy@oracle.com
+ * @since 2.6
+ */
+final class ReflectionUtils {
+
+ /**
+ * Non-instantiable utility class. Reflection instantiation permitted.
+ */
+ private ReflectionUtils() {
+ }
+
+ /**
+ * Retrieves class object.
+ * <p>
+ * If security is enabled, makes {@linkplain java.security.AccessController#doPrivileged(PrivilegedAction)
+ * privileged calls}.
+ *
+ * @param clazz name of class to be retrieved
+ * @return class object
+ * @see Class#forName(String)
+ */
+ static Class<?> forName(String clazz) throws ClassNotFoundException {
+ try {
+ return shouldUsePrivilegedAccess()
+ ? doPrivileged(ForNameIODH.PREDICATE_EXCEPTION_ACTION.with(clazz))
+ : forNameInternal(clazz);
+ } catch (PrivilegedActionException e) {
+ throw (ClassNotFoundException) e.getException();
+ }
+ }
+
+ /**
+ * Retrieves declared fields.
+ * <p>
+ * If security is enabled, makes {@linkplain java.security.AccessController#doPrivileged(PrivilegedAction)
+ * privileged calls}.
+ *
+ * @param clazz fields of that class will be returned
+ * @return array of declared fields
+ * @see Class#getDeclaredFields()
+ */
+ static Field[] getDeclaredFields(Class<?> clazz) {
+ return shouldUsePrivilegedAccess()
+ ? doPrivileged(DeclaredFieldsIODH.PREDICATE_ACTION.with(clazz))
+ : getDeclaredFieldsInternal(clazz);
+ }
+
+ /**
+ * Retrieves declared constructors.
+ *
+ * If security is enabled, makes {@linkplain java.security.AccessController#doPrivileged(PrivilegedAction)
+ * privileged calls}.
+ *
+ * @param clazz class that will be scanned
+ * @return declared constructors
+ * @see Class#getDeclaredConstructors()
+ */
+ static Constructor<?>[] getDeclaredConstructors(Class<?> clazz) {
+ return shouldUsePrivilegedAccess()
+ ? doPrivileged(DeclaredConstructorsIODH.PREDICATE_ACTION.with(clazz))
+ : getDeclaredConstructorsInternal(clazz);
+ }
+
+ /**
+ * Retrieves declared methods.
+ *
+ * If security is enabled, makes {@linkplain java.security.AccessController#doPrivileged(PrivilegedAction)
+ * privileged calls}.
+ *
+ * @param clazz class that will be scanned
+ * @return declared methods
+ * @see Class#getDeclaredMethods()
+ */
+ static Method[] getDeclaredMethods(Class<?> clazz) {
+ return shouldUsePrivilegedAccess()
+ ? doPrivileged(DeclaredMethodsIODH.PREDICATE_ACTION.with(clazz))
+ : getDeclaredMethodsInternal(clazz);
+ }
+
+ /**
+ * Retrieves declared method.
+ *
+ * If security is enabled, makes {@linkplain java.security.AccessController#doPrivileged(PrivilegedAction)
+ * privileged calls}.
+ *
+ * @param clazz class that will be scanned
+ * @param name name of the method to be retrieved
+ * @param parameterTypes parameter types of the method to be retrieved
+ * @return declared method
+ * @throws NoSuchMethodException if method was not found
+ * @see Class#getDeclaredMethod(String, Class...)
+ */
+ static Method getDeclaredMethod(Class<?> clazz, String name, Class<?>... parameterTypes) throws
+ NoSuchMethodException {
+ try {
+ return shouldUsePrivilegedAccess()
+ ? doPrivileged(DeclaredMethodIODH.PREDICATE_EXCEPTION_ACTION.with(clazz).with(name).with(parameterTypes))
+ : getDeclaredMethodInternal(clazz, name, parameterTypes);
+ } catch (PrivilegedActionException e) {
+ throw (NoSuchMethodException) e.getException();
+ }
+ }
+
+
+ /* Internal Methods */
+ /**
+ * INTERNAL:
+ */
+ private static Class<?> forNameInternal(String clazz) throws ClassNotFoundException {
+ return Class.forName(clazz);
+ }
+
+ /**
+ * INTERNAL:
+ */
+ private static Field[] getDeclaredFieldsInternal(Class<?> clazz) {
+ return clazz.getDeclaredFields();
+ }
+
+ /**
+ * INTERNAL:
+ */
+ private static Method[] getDeclaredMethodsInternal(Class<?> clazz) {
+ return clazz.getDeclaredMethods();
+ }
+
+ /**
+ * INTERNAL:
+ */
+ private static Constructor<?>[] getDeclaredConstructorsInternal(Class<?> clazz) {
+ return clazz.getDeclaredConstructors();
+ }
+
+ /**
+ * INTERNAL:
+ */
+ private static Method getDeclaredMethodInternal(Class<?> clazz, String name, Class<?>... parameterTypes) throws
+ NoSuchMethodException {
+ return clazz.getDeclaredMethod(name, parameterTypes);
+ }
+
+
+ /* Initialization on Demand Holders */
+ /**
+ * IODH for enhanced forName privileged action with exception using predicates.
+ */
+ private static final class ForNameIODH {
+
+ /**
+ * Enhanced {@link PrivilegedExceptionAction} using predicates.
+ * - Singleton.
+ * - Throws {@link java.lang.ClassNotFoundException}.
+ */
+ private static final PredicateWithException<Class<?>> PREDICATE_EXCEPTION_ACTION = new
+ PredicateWithException<Class<?>>() {
+
+ /* Predicates */
+ private String clazz;
+
+ @Override
+ public PredicateWithException<Class<?>> with(String with) {
+ this.clazz = with;
+ return this;
+ }
+
+ @Override
+ public PredicateWithException<Class<?>> with(Class<?> with) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public PredicateWithException<Class<?>> with(Class<?>[] with) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Class<?> run() throws NoSuchMethodException, ClassNotFoundException {
+ return forNameInternal(clazz);
+ }
+ };
+ }
+
+ /**
+ * IODH for enhanced getDeclaredFields privileged action using predicates.
+ */
+ private static final class DeclaredFieldsIODH {
+
+ /**
+ * Enhanced {@link java.security.PrivilegedAction} using predicates.
+ * - Singleton.
+ */
+ private static final Predicate<Field[]> PREDICATE_ACTION = new Predicate<Field[]>() {
+
+ /* Predicates */
+ private Class<?> clazz;
+
+ @Override
+ public Field[] run() {
+ return getDeclaredFieldsInternal(clazz);
+ }
+
+ @Override
+ public Predicate<Field[]> with(Class<?> clazz) {
+ this.clazz = clazz;
+ return this;
+ }
+ };
+ }
+
+ /**
+ * IODH for getDeclaredMethods privileged action using predicates.
+ */
+ private static final class DeclaredMethodsIODH {
+
+ /**
+ * Enhanced {@link PrivilegedAction} using predicates.
+ * - Singleton.
+ */
+ private static final Predicate<Method[]> PREDICATE_ACTION = new
+ Predicate<Method[]>() {
+
+ /* Predicates */
+ private Class<?> clazz;
+
+ @Override
+ public Predicate<Method[]> with(Class<?> with) {
+ this.clazz = with;
+ return this;
+ }
+
+ @Override
+ public Method[] run() {
+ return getDeclaredMethodsInternal(clazz);
+ }
+ };
+ }
+ /**
+ * IODH for getDeclaredConstructors privileged action using predicates.
+ */
+ private static final class DeclaredConstructorsIODH {
+
+ /**
+ * Enhanced {@link java.security.PrivilegedAction} using predicates.
+ * - Singleton.
+ */
+ private static final Predicate<Constructor<?>[]> PREDICATE_ACTION = new
+ Predicate<Constructor<?>[]>() {
+
+ /* Predicates */
+ private Class<?> clazz;
+
+ @Override
+ public Predicate<Constructor<?>[]> with(Class<?> with) {
+ this.clazz = with;
+ return this;
+ }
+
+ @Override
+ public Constructor<?>[] run() {
+ return getDeclaredConstructorsInternal(clazz);
+ }
+ };
+ }
+
+
+ /**
+ * IODH for getMethod predicate wrapped privileged exception action.
+ */
+ private static final class DeclaredMethodIODH {
+
+ /**
+ * Enhanced {@link PrivilegedExceptionAction} using predicates.
+ * - Singleton.
+ * - Throws {@link NoSuchMethodException}.
+ */
+ private static final PredicateWithException<Method> PREDICATE_EXCEPTION_ACTION = new
+ PredicateWithException<Method>() {
+
+ /* Predicates */
+ private Class<?> clazz;
+ private String name;
+ private Class<?>[] parameterTypes;
+
+ @Override
+ public PredicateWithException<Method> with(Class<?> with) {
+ this.clazz = with;
+ return this;
+ }
+
+ @Override
+ public PredicateWithException<Method> with(String with) {
+ this.name = with;
+ return this;
+ }
+
+ @Override
+ public PredicateWithException<Method> with(Class<?>[] with) {
+ this.parameterTypes = with;
+ return this;
+ }
+
+ @Override
+ public Method run() throws NoSuchMethodException {
+ return getDeclaredMethodInternal(clazz, name, parameterTypes);
+ }
+ };
+ }
+
+
+ /* Inner Interfaces */
+
+ /**
+ * Predicate-providing wrapper for {@link PrivilegedAction}.
+ *
+ * @param <T> return type of {@linkplain PrivilegedAction#run() computation}
+ */
+ private interface Predicate<T> extends PrivilegedAction<T> {
+
+ /**
+ * Assigns a predicate to the underlying privileged action.
+ * Any previous predicate of the same type will be overwritten.
+ *
+ * @param with predicate
+ * @return {@code this}
+ */
+ Predicate<T> with(Class<?> with);
+ }
+
+ /**
+ * Predicate-providing wrapper for {@link PrivilegedExceptionAction}.
+ *
+ * @param <T> return type of {@linkplain PrivilegedExceptionAction#run() computation}
+ */
+ private interface PredicateWithException<T> extends PrivilegedExceptionAction<T> {
+
+ /**
+ * Assigns a predicate to the underlying privileged exception action.
+ * Any previous predicate of the same type will be overwritten.
+ *
+ * @param with predicate
+ * @return {@code this}
+ */
+ PredicateWithException<T> with(Class<?> with);
+
+ /**
+ * Assigns a predicate to the underlying privileged exception action.
+ * Any previous predicate of the same type will be overwritten.
+ *
+ * @param with predicate
+ * @return {@code this}
+ */
+ PredicateWithException<T> with(String with);
+
+ /**
+ * Assigns a predicate to the underlying privileged exception action.
+ * Any previous predicate of the same type will be overwritten.
+ *
+ * @param with predicate
+ * @return {@code this}
+ */
+ PredicateWithException<T> with(Class<?>[] with);
+ }
+}
diff --git a/moxy/org.eclipse.persistence.moxy/src/org/eclipse/persistence/jaxb/ValidationXMLReader.java b/moxy/org.eclipse.persistence.moxy/src/org/eclipse/persistence/jaxb/ValidationXMLReader.java
new file mode 100644
index 0000000..1f26595
--- /dev/null
+++ b/moxy/org.eclipse.persistence.moxy/src/org/eclipse/persistence/jaxb/ValidationXMLReader.java
@@ -0,0 +1,208 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 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 v1.0 and Eclipse Distribution License v. 1.0
+ * which accompanies this distribution.
+ * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ * <p/>
+ * 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.security.AccessController;
+import java.security.PrivilegedActionException;
+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.eclipse.persistence.internal.security.PrivilegedGetContextClassLoader;
+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
+ * {@link org.eclipse.persistence.jaxb.BeanValidationHelper#constraintsOnClasses}
+ * with value {@link Boolean#TRUE}.
+ * <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 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() {
+ try {
+ return getThreadContextClassLoader().getResource(VALIDATION_XML) != null;
+ } catch (PrivilegedActionException ignored) {
+ LOGGER.log(Level.WARNING, "Loading of " + VALIDATION_XML + " file failed. ", ignored);
+ return false;
+ }
+ }
+
+ private void parseConstraintFiles() {
+ final DefaultHandler referencedFileHandler = new 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, referencedFileHandler);
+ }
+ }
+
+ /**
+ * 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 (PrivilegedActionException | SAXException | IOException ignored) {
+ LOGGER.log(Level.WARNING, "Parsing of validation.xml failed.", ignored);
+ }
+ }
+
+ private static ClassLoader getThreadContextClassLoader() throws PrivilegedActionException {
+ if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()) {
+ return AccessController.doPrivileged(new PrivilegedGetContextClassLoader(Thread.currentThread()));
+ } else {
+ return 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) throws SAXException {
+
+ if (CONSTRAINT_MAPPING_QNAME.equalsIgnoreCase(qName)) {
+ constraintsFileElement = true;
+ }
+ }
+
+ @Override
+ public void characters(char ch[], int start, int length) throws SAXException {
+
+ if (constraintsFileElement) {
+ constraintsFiles.add(new String(ch, start, length));
+ constraintsFileElement = false;
+ }
+ }
+ };
+
+}