diff options
author | Denis Anisimov <denis@vaadin.com> | 2016-09-18 09:53:39 +0300 |
---|---|---|
committer | Denis Anisimov <denis@vaadin.com> | 2016-09-23 11:08:56 +0000 |
commit | 0b5e2f34690a2a0f090a93ea1a63475a22ac3d13 (patch) | |
tree | a859f64029b513ee0c756e2b736d60fe2321dbab /server | |
parent | cef5b51d3ff0792ab0a8a8423549dbff79e3027b (diff) | |
download | vaadin-framework-0b5e2f34690a2a0f090a93ea1a63475a22ac3d13.tar.gz vaadin-framework-0b5e2f34690a2a0f090a93ea1a63475a22ac3d13.zip |
Extract validation availability check out of BeanValidator (#251).
BeanValidator uses and depends on javax.validation classes. So JSR303
presence check is extracted out of it to avoid ClassNotFoundException.
Change-Id: I8df9a9da873cf694a326c9abb05315c8e94a0a96
Diffstat (limited to 'server')
4 files changed, 192 insertions, 53 deletions
diff --git a/server/src/main/java/com/vaadin/data/BeanBinder.java b/server/src/main/java/com/vaadin/data/BeanBinder.java index b27e3ee4c3..c73b89de81 100644 --- a/server/src/main/java/com/vaadin/data/BeanBinder.java +++ b/server/src/main/java/com/vaadin/data/BeanBinder.java @@ -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; } diff --git a/server/src/main/java/com/vaadin/data/util/BeanUtil.java b/server/src/main/java/com/vaadin/data/util/BeanUtil.java index 579eb08b68..763e6ec4b1 100644 --- a/server/src/main/java/com/vaadin/data/util/BeanUtil.java +++ b/server/src/main/java/com/vaadin/data/util/BeanUtil.java @@ -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; + } + } + } } diff --git a/server/src/main/java/com/vaadin/data/validator/BeanValidator.java b/server/src/main/java/com/vaadin/data/validator/BeanValidator.java index 3b779b822d..9ac9c9a649 100644 --- a/server/src/main/java/com/vaadin/data/validator/BeanValidator.java +++ b/server/src/main/java/com/vaadin/data/validator/BeanValidator.java @@ -16,11 +16,11 @@ 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 index 0000000000..876fb534a3 --- /dev/null +++ b/server/src/test/java/com/vaadin/data/Jsr303Test.java @@ -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(); + } + +} |