]> source.dussan.org Git - vaadin-framework.git/commitdiff
Extract validation availability check out of BeanValidator (#251).
authorDenis Anisimov <denis@vaadin.com>
Sun, 18 Sep 2016 06:53:39 +0000 (09:53 +0300)
committerDenis Anisimov <denis@vaadin.com>
Fri, 23 Sep 2016 11:08:56 +0000 (11:08 +0000)
BeanValidator uses and depends on javax.validation classes. So JSR303
presence check is extracted out of it to avoid ClassNotFoundException.

Change-Id: I8df9a9da873cf694a326c9abb05315c8e94a0a96

server/src/main/java/com/vaadin/data/BeanBinder.java
server/src/main/java/com/vaadin/data/util/BeanUtil.java
server/src/main/java/com/vaadin/data/validator/BeanValidator.java
server/src/test/java/com/vaadin/data/Jsr303Test.java [new file with mode: 0644]

index b27e3ee4c30e24b10cdf6718aa3d5b32c0978b6a..c73b89de812fb97c245a92517b84565bc50f9de8 100644 (file)
@@ -176,7 +176,7 @@ public class BeanBinder<BEAN> extends Binder<BEAN> {
 
             finalBinding = withConverter(createConverter());
 
-            if (BeanValidator.checkBeanValidationAvailable()) {
+            if (BeanUtil.checkBeanValidationAvailable()) {
                 finalBinding = finalBinding.withValidator(new BeanValidator(
                         getBinder().beanType, propertyName, findLocale()));
             }
@@ -262,7 +262,7 @@ public class BeanBinder<BEAN> extends Binder<BEAN> {
      *            the bean {@code Class} instance, not null
      */
     public BeanBinder(Class<? extends BEAN> beanType) {
-        BeanValidator.checkBeanValidationAvailable();
+        BeanUtil.checkBeanValidationAvailable();
         this.beanType = beanType;
     }
 
index 579eb08b68d637677b082430a65395df11499425..763e6ec4b1a239f35b403bc6bd3abf00e7f0bc36 100644 (file)
@@ -23,6 +23,9 @@ import java.io.Serializable;
 import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.logging.Logger;
+
+import com.vaadin.data.validator.BeanValidator;
 
 /**
  * Utility class for Java Beans information access.
@@ -145,6 +148,20 @@ public final class BeanUtil implements Serializable {
         }
     }
 
+    /**
+     * Returns whether an implementation of JSR-303 version 1.0 or 1.1 is
+     * present on the classpath. If this method returns false, trying to create
+     * a {@code BeanValidator} instance will throw an
+     * {@code IllegalStateException}. If an implementation is not found, logs a
+     * level {@code FINE} message the first time it is run.
+     *
+     * @return {@code true} if bean validation is available, {@code false}
+     *         otherwise.
+     */
+    public static boolean checkBeanValidationAvailable() {
+        return LazyValidationAvailability.BEAN_VALIDATION_AVAILABLE;
+    }
+
     // Workaround for Java6 bug JDK-6788525. Do nothing for JDK7+.
     private static List<PropertyDescriptor> getPropertyDescriptors(
             BeanInfo beanInfo) {
@@ -206,4 +223,21 @@ public final class BeanUtil implements Serializable {
             return null;
         }
     }
+
+    private static class LazyValidationAvailability implements Serializable {
+        private static final boolean BEAN_VALIDATION_AVAILABLE = isAvailable();
+
+        private static boolean isAvailable() {
+            try {
+                Class.forName("javax.validation.Validation");
+                return true;
+            } catch (ClassNotFoundException e) {
+                Logger.getLogger(BeanValidator.class.getName())
+                        .fine("A JSR-303 bean validation implementation not found on the classpath. "
+                                + BeanValidator.class.getSimpleName()
+                                + " cannot be used.");
+                return false;
+            }
+        }
+    }
 }
index 3b779b822db3fe0038591377a873a37f9a3d0a64..9ac9c9a649a683a7965a0c8b33755cdaeb736f12 100644 (file)
 
 package com.vaadin.data.validator;
 
+import java.io.Serializable;
 import java.util.Locale;
 import java.util.Objects;
 import java.util.Set;
 import java.util.function.BinaryOperator;
-import java.util.logging.Logger;
 
 import javax.validation.ConstraintViolation;
 import javax.validation.MessageInterpolator.Context;
@@ -30,6 +30,7 @@ import javax.validation.metadata.ConstraintDescriptor;
 
 import com.vaadin.data.Result;
 import com.vaadin.data.Validator;
+import com.vaadin.data.util.BeanUtil;
 
 /**
  * A {@code Validator} using the JSR-303 (javax.validation) annotation-based
@@ -43,46 +44,36 @@ import com.vaadin.data.Validator;
  * project classpath when using bean validation. Specification versions 1.0 and
  * 1.1 are supported.
  *
- * @author Petri Hakala
  * @author Vaadin Ltd.
  *
  * @since 8.0
  */
 public class BeanValidator implements Validator<Object> {
 
-    private static volatile Boolean beanValidationAvailable;
-    private static ValidatorFactory factory;
+    private static final class ContextImpl implements Context, Serializable {
 
-    private String propertyName;
-    private Class<?> beanType;
-    private Locale locale;
+        private final ConstraintViolation<?> violation;
 
-    /**
-     * Returns whether an implementation of JSR-303 version 1.0 or 1.1 is
-     * present on the classpath. If this method returns false, trying to create
-     * a {@code BeanValidator} instance will throw an
-     * {@code IllegalStateException}. If an implementation is not found, logs a
-     * level {@code FINE} message the first time it is run.
-     *
-     * @return {@code true} if bean validation is available, {@code false}
-     *         otherwise.
-     */
-    public static boolean checkBeanValidationAvailable() {
-        if (beanValidationAvailable == null) {
-            try {
-                Class.forName(Validation.class.getName());
-                beanValidationAvailable = true;
-            } catch (ClassNotFoundException e) {
-                Logger.getLogger(BeanValidator.class.getName())
-                        .fine("A JSR-303 bean validation implementation not found on the classpath. "
-                                + BeanValidator.class.getSimpleName()
-                                + " cannot be used.");
-                beanValidationAvailable = false;
-            }
+        private ContextImpl(ConstraintViolation<?> violation) {
+            this.violation = violation;
         }
-        return beanValidationAvailable;
+
+        @Override
+        public ConstraintDescriptor<?> getConstraintDescriptor() {
+            return violation.getConstraintDescriptor();
+        }
+
+        @Override
+        public Object getValidatedValue() {
+            return violation.getInvalidValue();
+        }
+
     }
 
+    private String propertyName;
+    private Class<?> beanType;
+    private Locale locale;
+
     /**
      * Creates a new JSR-303 {@code BeanValidator} that validates values of the
      * specified property. Localizes validation messages using the
@@ -93,7 +84,8 @@ public class BeanValidator implements Validator<Object> {
      * @param propertyName
      *            the property to validate, not null
      * @throws IllegalStateException
-     *             if {@link #checkBeanValidationAvailable()} returns false
+     *             if {@link BeanUtil#checkBeanValidationAvailable()} returns
+     *             false
      */
     public BeanValidator(Class<?> beanType, String propertyName) {
         this(beanType, propertyName, Locale.getDefault());
@@ -110,11 +102,12 @@ public class BeanValidator implements Validator<Object> {
      * @param locale
      *            the locale to use, not null
      * @throws IllegalStateException
-     *             if {@link #checkBeanValidationAvailable()} returns false
+     *             if {@link BeanUtil#checkBeanValidationAvailable()} returns
+     *             false
      */
     public BeanValidator(Class<?> beanType, String propertyName,
             Locale locale) {
-        if (!checkBeanValidationAvailable()) {
+        if (!BeanUtil.checkBeanValidationAvailable()) {
             throw new IllegalStateException("Cannot create a "
                     + BeanValidator.class.getSimpleName()
                     + ": a JSR-303 Bean Validation implementation not found on theclasspath");
@@ -170,11 +163,7 @@ public class BeanValidator implements Validator<Object> {
      * @return the validator factory to use
      */
     protected static ValidatorFactory getJavaxBeanValidatorFactory() {
-        if (factory == null) {
-            checkBeanValidationAvailable();
-            factory = Validation.buildDefaultValidatorFactory();
-        }
-        return factory;
+        return LazyFactoryInitializer.FACTORY;
     }
 
     /**
@@ -203,22 +192,12 @@ public class BeanValidator implements Validator<Object> {
      * Creates a simple message interpolation context based on the given
      * constraint violation.
      *
-     * @param v
+     * @param violation
      *            the constraint violation
      * @return the message interpolation context
      */
-    protected Context createContext(ConstraintViolation<?> v) {
-        return new Context() {
-            @Override
-            public ConstraintDescriptor<?> getConstraintDescriptor() {
-                return v.getConstraintDescriptor();
-            }
-
-            @Override
-            public Object getValidatedValue() {
-                return v.getInvalidValue();
-            }
-        };
+    protected Context createContext(ConstraintViolation<?> violation) {
+        return new ContextImpl(violation);
     }
 
     /**
@@ -232,4 +211,12 @@ public class BeanValidator implements Validator<Object> {
         Objects.requireNonNull(locale, "locale cannot be null");
         this.locale = locale;
     }
+
+    private static class LazyFactoryInitializer implements Serializable {
+        private static final ValidatorFactory FACTORY = getFactory();
+
+        private static ValidatorFactory getFactory() {
+            return Validation.buildDefaultValidatorFactory();
+        }
+    }
 }
diff --git a/server/src/test/java/com/vaadin/data/Jsr303Test.java b/server/src/test/java/com/vaadin/data/Jsr303Test.java
new file mode 100644 (file)
index 0000000..876fb53
--- /dev/null
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.data;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.net.URL;
+import java.net.URLClassLoader;
+
+import javax.validation.Validation;
+
+import org.apache.commons.io.IOUtils;
+import org.junit.Assert;
+import org.junit.Test;
+
+import com.vaadin.data.util.BeanUtil;
+import com.vaadin.tests.data.bean.BeanToValidate;
+import com.vaadin.ui.TextField;
+
+/**
+ * @author Vaadin Ltd
+ *
+ */
+public class Jsr303Test {
+
+    private static class TestClassLoader extends URLClassLoader {
+
+        public TestClassLoader() {
+            super(new URL[0], Thread.currentThread().getContextClassLoader());
+        }
+
+        @Override
+        public Class<?> loadClass(String name) throws ClassNotFoundException {
+            String vaadinPackagePrefix = getClass().getPackage().getName();
+            vaadinPackagePrefix = vaadinPackagePrefix.substring(0,
+                    vaadinPackagePrefix.lastIndexOf('.'));
+            if (name.equals(UnitTest.class.getName())) {
+                super.loadClass(name);
+            } else if (name
+                    .startsWith(Validation.class.getPackage().getName())) {
+                throw new ClassNotFoundException();
+            } else if (name.startsWith(vaadinPackagePrefix)) {
+                String path = name.replace('.', '/').concat(".class");
+                URL resource = Thread.currentThread().getContextClassLoader()
+                        .getResource(path);
+                InputStream stream;
+                try {
+                    stream = resource.openStream();
+                    byte[] bytes = IOUtils.toByteArray(stream);
+                    return defineClass(name, bytes, 0, bytes.length);
+                } catch (IOException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+            return super.loadClass(name);
+        }
+    }
+
+    public interface UnitTest {
+        void execute();
+    }
+
+    public static class Jsr303UnitTest implements UnitTest {
+
+        private TextField nameField = new TextField();
+
+        @Override
+        public void execute() {
+            Assert.assertFalse(BeanUtil.checkBeanValidationAvailable());
+
+            BeanBinder<BeanToValidate> binder = new BeanBinder<>(
+                    BeanToValidate.class);
+            BeanToValidate item = new BeanToValidate();
+            String name = "Johannes";
+            item.setFirstname(name);
+            item.setAge(32);
+
+            binder.bind(nameField, "firstname");
+            binder.bind(item);
+
+            assertEquals(name, nameField.getValue());
+
+            // BeanToValidate : @Size(min = 3, max = 16) for the firstName
+            nameField.setValue("a");
+            assertEquals(nameField.getValue(), item.getFirstname());
+        }
+
+    }
+
+    @Test
+    public void beanBinderWithoutJsr303() throws ClassNotFoundException,
+            NoSuchMethodException, SecurityException, InstantiationException,
+            IllegalAccessException, IllegalArgumentException,
+            InvocationTargetException, IOException, InterruptedException {
+        URLClassLoader loader = new TestClassLoader();
+        Class<?> clazz = loader.loadClass(Jsr303UnitTest.class.getName());
+        UnitTest test = (UnitTest) clazz.newInstance();
+        test.execute();
+        loader.close();
+    }
+
+}