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;
+            }
+        }
+    };
+
+}