Skip to content

Commit

Permalink
Extract validation availability check out of BeanValidator (#251).
Browse files Browse the repository at this point in the history
BeanValidator uses and depends on javax.validation classes. So JSR303
presence check is extracted out of it to avoid ClassNotFoundException.

Change-Id: I8df9a9da873cf694a326c9abb05315c8e94a0a96
  • Loading branch information
Denis Anisimov committed Sep 23, 2016
1 parent cef5b51 commit 0b5e2f3
Show file tree
Hide file tree
Showing 4 changed files with 192 additions and 53 deletions.
4 changes: 2 additions & 2 deletions server/src/main/java/com/vaadin/data/BeanBinder.java
Expand Up @@ -176,7 +176,7 @@ public void bind(String propertyName) {


finalBinding = withConverter(createConverter()); finalBinding = withConverter(createConverter());


if (BeanValidator.checkBeanValidationAvailable()) { if (BeanUtil.checkBeanValidationAvailable()) {
finalBinding = finalBinding.withValidator(new BeanValidator( finalBinding = finalBinding.withValidator(new BeanValidator(
getBinder().beanType, propertyName, findLocale())); getBinder().beanType, propertyName, findLocale()));
} }
Expand Down Expand Up @@ -262,7 +262,7 @@ private <T> T cast(TARGET value, Class<T> clazz) {
* the bean {@code Class} instance, not null * the bean {@code Class} instance, not null
*/ */
public BeanBinder(Class<? extends BEAN> beanType) { public BeanBinder(Class<? extends BEAN> beanType) {
BeanValidator.checkBeanValidationAvailable(); BeanUtil.checkBeanValidationAvailable();
this.beanType = beanType; this.beanType = beanType;
} }


Expand Down
34 changes: 34 additions & 0 deletions server/src/main/java/com/vaadin/data/util/BeanUtil.java
Expand Up @@ -23,6 +23,9 @@
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.logging.Logger;

import com.vaadin.data.validator.BeanValidator;


/** /**
* Utility class for Java Beans information access. * Utility class for Java Beans information access.
Expand Down Expand Up @@ -145,6 +148,20 @@ public static PropertyDescriptor getPropertyDescriptor(Class<?> beanType,
} }
} }


/**
* 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+. // Workaround for Java6 bug JDK-6788525. Do nothing for JDK7+.
private static List<PropertyDescriptor> getPropertyDescriptors( private static List<PropertyDescriptor> getPropertyDescriptors(
BeanInfo beanInfo) { BeanInfo beanInfo) {
Expand Down Expand Up @@ -206,4 +223,21 @@ private static Method getMethodFromBridge(Method bridgeMethod,
return null; 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;
}
}
}
} }
89 changes: 38 additions & 51 deletions server/src/main/java/com/vaadin/data/validator/BeanValidator.java
Expand Up @@ -16,11 +16,11 @@


package com.vaadin.data.validator; package com.vaadin.data.validator;


import java.io.Serializable;
import java.util.Locale; import java.util.Locale;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.function.BinaryOperator; import java.util.function.BinaryOperator;
import java.util.logging.Logger;


import javax.validation.ConstraintViolation; import javax.validation.ConstraintViolation;
import javax.validation.MessageInterpolator.Context; import javax.validation.MessageInterpolator.Context;
Expand All @@ -30,6 +30,7 @@


import com.vaadin.data.Result; import com.vaadin.data.Result;
import com.vaadin.data.Validator; import com.vaadin.data.Validator;
import com.vaadin.data.util.BeanUtil;


/** /**
* A {@code Validator} using the JSR-303 (javax.validation) annotation-based * A {@code Validator} using the JSR-303 (javax.validation) annotation-based
Expand All @@ -43,46 +44,36 @@
* project classpath when using bean validation. Specification versions 1.0 and * project classpath when using bean validation. Specification versions 1.0 and
* 1.1 are supported. * 1.1 are supported.
* *
* @author Petri Hakala
* @author Vaadin Ltd. * @author Vaadin Ltd.
* *
* @since 8.0 * @since 8.0
*/ */
public class BeanValidator implements Validator<Object> { public class BeanValidator implements Validator<Object> {


private static volatile Boolean beanValidationAvailable; private static final class ContextImpl implements Context, Serializable {
private static ValidatorFactory factory;


private String propertyName; private final ConstraintViolation<?> violation;
private Class<?> beanType;
private Locale locale;


/** private ContextImpl(ConstraintViolation<?> violation) {
* Returns whether an implementation of JSR-303 version 1.0 or 1.1 is this.violation = violation;
* 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;
}
} }
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 * Creates a new JSR-303 {@code BeanValidator} that validates values of the
* specified property. Localizes validation messages using the * specified property. Localizes validation messages using the
Expand All @@ -93,7 +84,8 @@ public static boolean checkBeanValidationAvailable() {
* @param propertyName * @param propertyName
* the property to validate, not null * the property to validate, not null
* @throws IllegalStateException * @throws IllegalStateException
* if {@link #checkBeanValidationAvailable()} returns false * if {@link BeanUtil#checkBeanValidationAvailable()} returns
* false
*/ */
public BeanValidator(Class<?> beanType, String propertyName) { public BeanValidator(Class<?> beanType, String propertyName) {
this(beanType, propertyName, Locale.getDefault()); this(beanType, propertyName, Locale.getDefault());
Expand All @@ -110,11 +102,12 @@ public BeanValidator(Class<?> beanType, String propertyName) {
* @param locale * @param locale
* the locale to use, not null * the locale to use, not null
* @throws IllegalStateException * @throws IllegalStateException
* if {@link #checkBeanValidationAvailable()} returns false * if {@link BeanUtil#checkBeanValidationAvailable()} returns
* false
*/ */
public BeanValidator(Class<?> beanType, String propertyName, public BeanValidator(Class<?> beanType, String propertyName,
Locale locale) { Locale locale) {
if (!checkBeanValidationAvailable()) { if (!BeanUtil.checkBeanValidationAvailable()) {
throw new IllegalStateException("Cannot create a " throw new IllegalStateException("Cannot create a "
+ BeanValidator.class.getSimpleName() + BeanValidator.class.getSimpleName()
+ ": a JSR-303 Bean Validation implementation not found on theclasspath"); + ": a JSR-303 Bean Validation implementation not found on theclasspath");
Expand Down Expand Up @@ -170,11 +163,7 @@ public String toString() {
* @return the validator factory to use * @return the validator factory to use
*/ */
protected static ValidatorFactory getJavaxBeanValidatorFactory() { protected static ValidatorFactory getJavaxBeanValidatorFactory() {
if (factory == null) { return LazyFactoryInitializer.FACTORY;
checkBeanValidationAvailable();
factory = Validation.buildDefaultValidatorFactory();
}
return factory;
} }


/** /**
Expand Down Expand Up @@ -203,22 +192,12 @@ protected String getMessage(ConstraintViolation<?> v) {
* Creates a simple message interpolation context based on the given * Creates a simple message interpolation context based on the given
* constraint violation. * constraint violation.
* *
* @param v * @param violation
* the constraint violation * the constraint violation
* @return the message interpolation context * @return the message interpolation context
*/ */
protected Context createContext(ConstraintViolation<?> v) { protected Context createContext(ConstraintViolation<?> violation) {
return new Context() { return new ContextImpl(violation);
@Override
public ConstraintDescriptor<?> getConstraintDescriptor() {
return v.getConstraintDescriptor();
}

@Override
public Object getValidatedValue() {
return v.getInvalidValue();
}
};
} }


/** /**
Expand All @@ -232,4 +211,12 @@ private void setLocale(Locale locale) {
Objects.requireNonNull(locale, "locale cannot be null"); Objects.requireNonNull(locale, "locale cannot be null");
this.locale = locale; this.locale = locale;
} }

private static class LazyFactoryInitializer implements Serializable {
private static final ValidatorFactory FACTORY = getFactory();

private static ValidatorFactory getFactory() {
return Validation.buildDefaultValidatorFactory();
}
}
} }
118 changes: 118 additions & 0 deletions 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();
}

}

0 comments on commit 0b5e2f3

Please sign in to comment.