diff options
Diffstat (limited to 'compatibility-server/src/main/java/com/vaadin/data/fieldgroup/BeanFieldGroup.java')
-rw-r--r-- | compatibility-server/src/main/java/com/vaadin/data/fieldgroup/BeanFieldGroup.java | 272 |
1 files changed, 272 insertions, 0 deletions
diff --git a/compatibility-server/src/main/java/com/vaadin/data/fieldgroup/BeanFieldGroup.java b/compatibility-server/src/main/java/com/vaadin/data/fieldgroup/BeanFieldGroup.java new file mode 100644 index 0000000000..96e4621761 --- /dev/null +++ b/compatibility-server/src/main/java/com/vaadin/data/fieldgroup/BeanFieldGroup.java @@ -0,0 +1,272 @@ +/* + * 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.fieldgroup; + +import java.beans.IntrospectionException; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; + +import com.vaadin.data.Item; +import com.vaadin.data.util.BeanItem; +import com.vaadin.data.util.BeanUtil; +import com.vaadin.v7.data.validator.LegacyBeanValidator; +import com.vaadin.v7.ui.LegacyField; + +public class BeanFieldGroup<T> extends FieldGroup { + + private final Class<T> beanType; + + private static Boolean beanValidationImplementationAvailable = null; + private final Map<LegacyField<?>, LegacyBeanValidator> defaultValidators; + + public BeanFieldGroup(Class<T> beanType) { + this.beanType = beanType; + this.defaultValidators = new HashMap<LegacyField<?>, LegacyBeanValidator>(); + } + + @Override + protected Class<?> getPropertyType(Object propertyId) { + if (getItemDataSource() != null) { + return super.getPropertyType(propertyId); + } else { + // Data source not set so we need to figure out the type manually + /* + * toString should never really be needed as propertyId should be of + * form "fieldName" or "fieldName.subField[.subField2]" but the + * method declaration comes from parent. + */ + try { + Class<?> type = BeanUtil.getPropertyType(beanType, + propertyId.toString()); + if (type == null) { + throw new BindException( + "Cannot determine type of propertyId '" + propertyId + + "'. The propertyId was not found in " + + beanType.getName()); + } + return type; + } catch (IntrospectionException e) { + throw new BindException("Cannot determine type of propertyId '" + + propertyId + "'. Unable to introspect " + beanType, + e); + } + } + } + + @Override + protected Object findPropertyId(java.lang.reflect.Field memberField) { + String fieldName = memberField.getName(); + Item dataSource = getItemDataSource(); + if (dataSource != null + && dataSource.getItemProperty(fieldName) != null) { + return fieldName; + } else { + String minifiedFieldName = minifyFieldName(fieldName); + try { + return getFieldName(beanType, minifiedFieldName); + } catch (SecurityException e) { + } catch (NoSuchFieldException e) { + } + } + return null; + } + + private static String getFieldName(Class<?> cls, String propertyId) + throws SecurityException, NoSuchFieldException { + for (java.lang.reflect.Field field1 : cls.getDeclaredFields()) { + if (propertyId.equals(minifyFieldName(field1.getName()))) { + return field1.getName(); + } + } + // Try super classes until we reach Object + Class<?> superClass = cls.getSuperclass(); + if (superClass != null && superClass != Object.class) { + return getFieldName(superClass, propertyId); + } else { + throw new NoSuchFieldException(); + } + } + + /** + * Helper method for setting the data source directly using a bean. This + * method wraps the bean in a {@link BeanItem} and calls + * {@link #setItemDataSource(Item)}. + * <p> + * For null values, a null item is passed to + * {@link #setItemDataSource(Item)} to be properly clear fields. + * + * @param bean + * The bean to use as data source. + */ + public void setItemDataSource(T bean) { + if (bean == null) { + setItemDataSource((Item) null); + } else { + setItemDataSource(new BeanItem<T>(bean, beanType)); + } + } + + @Override + public void setItemDataSource(Item item) { + if (item == null || (item instanceof BeanItem)) { + super.setItemDataSource(item); + } else { + throw new RuntimeException(getClass().getSimpleName() + + " only supports BeanItems as item data source"); + } + } + + @Override + public BeanItem<T> getItemDataSource() { + return (BeanItem<T>) super.getItemDataSource(); + } + + private void ensureNestedPropertyAdded(Object propertyId) { + if (getItemDataSource() != null) { + // The data source is set so the property must be found in the item. + // If it is not we try to add it. + try { + getItemProperty(propertyId); + } catch (BindException e) { + // Not found, try to add a nested property; + // BeanItem property ids are always strings so this is safe + getItemDataSource().addNestedProperty((String) propertyId); + } + } + } + + @Override + public void bind(LegacyField field, Object propertyId) { + ensureNestedPropertyAdded(propertyId); + super.bind(field, propertyId); + } + + @Override + public <T extends LegacyField> T buildAndBind(String caption, + Object propertyId, Class<T> fieldType) throws BindException { + ensureNestedPropertyAdded(propertyId); + return super.buildAndBind(caption, propertyId, fieldType); + } + + @Override + public void unbind(LegacyField<?> field) throws BindException { + super.unbind(field); + + LegacyBeanValidator removed = defaultValidators.remove(field); + if (removed != null) { + field.removeValidator(removed); + } + } + + @Override + protected void configureField(LegacyField<?> field) { + super.configureField(field); + // Add Bean validators if there are annotations + if (isBeanValidationImplementationAvailable() + && !defaultValidators.containsKey(field)) { + LegacyBeanValidator validator = new LegacyBeanValidator(beanType, + getPropertyId(field).toString()); + field.addValidator(validator); + if (field.getLocale() != null) { + validator.setLocale(field.getLocale()); + } + defaultValidators.put(field, validator); + } + } + + /** + * Checks whether a bean validation implementation (e.g. Hibernate Validator + * or Apache Bean Validation) is available. + * + * TODO move this method to some more generic location + * + * @return true if a JSR-303 bean validation implementation is available + */ + protected static boolean isBeanValidationImplementationAvailable() { + if (beanValidationImplementationAvailable != null) { + return beanValidationImplementationAvailable; + } + try { + Class<?> validationClass = Class + .forName("javax.validation.Validation"); + Method buildFactoryMethod = validationClass + .getMethod("buildDefaultValidatorFactory"); + Object factory = buildFactoryMethod.invoke(null); + beanValidationImplementationAvailable = (factory != null); + } catch (Exception e) { + // no bean validation implementation available + beanValidationImplementationAvailable = false; + } + return beanValidationImplementationAvailable; + } + + /** + * Convenience method to bind Fields from a given "field container" to a + * given bean with buffering disabled. + * <p> + * The returned {@link BeanFieldGroup} can be used for further + * configuration. + * + * @see #bindFieldsBuffered(Object, Object) + * @see #bindMemberFields(Object) + * @since 7.2 + * @param bean + * the bean to be bound + * @param objectWithMemberFields + * the class that contains {@link LegacyField}s for bean + * properties + * @return the bean field group used to make binding + */ + public static <T> BeanFieldGroup<T> bindFieldsUnbuffered(T bean, + Object objectWithMemberFields) { + return createAndBindFields(bean, objectWithMemberFields, false); + } + + /** + * Convenience method to bind Fields from a given "field container" to a + * given bean with buffering enabled. + * <p> + * The returned {@link BeanFieldGroup} can be used for further + * configuration. + * + * @see #bindFieldsUnbuffered(Object, Object) + * @see #bindMemberFields(Object) + * @since 7.2 + * @param bean + * the bean to be bound + * @param objectWithMemberFields + * the class that contains {@link LegacyField}s for bean + * properties + * @return the bean field group used to make binding + */ + public static <T> BeanFieldGroup<T> bindFieldsBuffered(T bean, + Object objectWithMemberFields) { + return createAndBindFields(bean, objectWithMemberFields, true); + } + + private static <T> BeanFieldGroup<T> createAndBindFields(T bean, + Object objectWithMemberFields, boolean buffered) { + @SuppressWarnings("unchecked") + BeanFieldGroup<T> beanFieldGroup = new BeanFieldGroup<T>( + (Class<T>) bean.getClass()); + beanFieldGroup.setItemDataSource(bean); + beanFieldGroup.setBuffered(buffered); + beanFieldGroup.bindMemberFields(objectWithMemberFields); + return beanFieldGroup; + } + +} |