Browse Source

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
tags/8.0.0.alpha3
Denis Anisimov 7 years ago
parent
commit
0b5e2f3469

+ 2
- 2
server/src/main/java/com/vaadin/data/BeanBinder.java View 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;
}


+ 34
- 0
server/src/main/java/com/vaadin/data/util/BeanUtil.java View 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;
}
}
}
}

+ 38
- 51
server/src/main/java/com/vaadin/data/validator/BeanValidator.java View File

@@ -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();
}
}
}

+ 118
- 0
server/src/test/java/com/vaadin/data/Jsr303Test.java View File

@@ -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();
}

}

Loading…
Cancel
Save