BeanValidator uses and depends on javax.validation classes. So JSR303 presence check is extracted out of it to avoid ClassNotFoundException. Change-Id: I8df9a9da873cf694a326c9abb05315c8e94a0a96tags/8.0.0.alpha3
@@ -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; | |||
} | |||
@@ -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; | |||
} | |||
} | |||
} | |||
} |
@@ -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(); | |||
} | |||
} | |||
} |
@@ -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(); | |||
} | |||
} |