package enterpriseapp.ui.crud;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Set;
import javax.persistence.Column;
import javax.persistence.JoinColumn;
import org.hibernate.annotations.Type;
import com.vaadin.data.Container;
import com.vaadin.data.Item;
import com.vaadin.data.util.BeanItem;
import com.vaadin.data.validator.DoubleValidator;
import com.vaadin.data.validator.EmailValidator;
import com.vaadin.data.validator.IntegerValidator;
import com.vaadin.data.validator.StringLengthValidator;
import com.vaadin.ui.AbstractTextField;
import com.vaadin.ui.Component;
import com.vaadin.ui.DateField;
import com.vaadin.ui.DefaultFieldFactory;
import com.vaadin.ui.Field;
import com.vaadin.ui.InlineDateField;
import com.vaadin.ui.PasswordField;
import com.vaadin.ui.Upload.FinishedEvent;
import enterpriseapp.EnterpriseApplication;
import enterpriseapp.Utils;
import enterpriseapp.hibernate.ContainerFactory;
import enterpriseapp.hibernate.CustomHbnContainer.EntityItem;
import enterpriseapp.hibernate.DefaultHbnContainer;
import enterpriseapp.hibernate.annotation.CrudField;
import enterpriseapp.hibernate.annotation.CrudTable;
import enterpriseapp.hibernate.annotation.Downloadable;
import enterpriseapp.hibernate.dto.Dto;
import enterpriseapp.ui.Constants;
import enterpriseapp.ui.DownloadField;
import enterpriseapp.ui.validator.LongValidator;
/**
*
* A default FieldFactory for CrudComponents.
*
* @author Alejandro Duarte
*
*/
public class DefaultCrudFieldFactory extends DefaultFieldFactory {
private static final long serialVersionUID = 1L;
/**
* Override this to create your custom fields.
* @param bean current bean (Entity/Dto) for which you want to build the field.
* @param item current item for which you want to build the field.
* @param pid property for which you want to build the field.
* @param uiContext the component where the field is presented.
* @param propertyType the property type for which you want to build the field.
* @return null if no custom Field is created.
*/
public Field createCustomField(Object bean, Item item, String pid, Component uiContext, Class<?> propertyType) {
return null;
}
/**
* Configures the specified Field (adds caption, validators, etc.).
* @param field Field to configure.
* @param bean bean to which the field belongs.
* @param item item to which the field belongs.
* @param pid field property.
* @param uiContext the component where the field is presented.
* @param propertyType the field property type.
* @param crudFieldAnnotation CrudFieldAnnotation annotation (if present).
* @param columnAnnotation ColumnAnnotation annotation (if present).
* @param joinColumnAnnotation ColumnAnnotation annotation (if present).
* @param typeAnnotation TypeAnnotation (if present)
*/
public void configureField(Field field, Object bean, Item item, String pid, Component uiContext, Class<?> propertyType, CrudField crudFieldAnnotation, Column columnAnnotation, JoinColumn joinColumnAnnotation, Type typeAnnotation) {
field.setCaption(getFieldCaption(pid, bean.getClass()));
checkRequired(field, crudFieldAnnotation, columnAnnotation, joinColumnAnnotation);
checkLength(field, columnAnnotation, typeAnnotation);
checkNullRepresentation(field);
addValidators(field, bean, item, pid, uiContext, propertyType, crudFieldAnnotation);
field.setWidth("100%");
}
@SuppressWarnings("rawtypes")
@Override
public Field createField(Item item, Object propertyId, Component uiContext) {
Field field = null;
if(uiContext == null) {
return null;
}
try {
String pid = propertyId.toString();
BeanItem beanItem = (BeanItem) item;
Object bean = beanItem.getBean();
Class<?> propertyType = bean.getClass().getDeclaredField(propertyId.toString()).getType();
java.lang.reflect.Field declaredField = bean.getClass().getDeclaredField(pid);
CrudField crudFieldAnnotation = declaredField.getAnnotation(CrudField.class);
Column columnAnnotation = declaredField.getAnnotation(Column.class);
JoinColumn joinColumnAnnotation = declaredField.getAnnotation(JoinColumn.class);
Downloadable downloadableAnnotation = declaredField.getAnnotation(Downloadable.class);
Type typeAnnotation = declaredField.getAnnotation(Type.class);
List<Object> visibleProperties = null;
boolean propertiesDefined = false;
if(CrudForm.class.isAssignableFrom(uiContext.getClass())) {
Object[] visibleFormProperties = Utils.getVisibleFormProperties(bean.getClass());
if(visibleFormProperties != null) {
visibleProperties = Arrays.asList(visibleFormProperties);
propertiesDefined = true;
}
} else if(enterpriseapp.ui.crud.CrudTable.class.isAssignableFrom(uiContext.getClass())) {
Object[] visibleTableProperties = Utils.getVisibleTableProperties(bean.getClass());
if(visibleTableProperties != null) {
visibleProperties = Arrays.asList(visibleTableProperties);
propertiesDefined = true;
}
}
if(propertiesDefined) {
if(visibleProperties == null || visibleProperties.isEmpty()) {
return null;
}
if(!visibleProperties.contains(propertyId)) {
return null;
}
}
field = createCustomField(beanItem.getBean(), item, pid, uiContext, propertyType);
if(field == null) {
field = createEmbeddedTableField(propertyId, bean, propertyType, crudFieldAnnotation);
}
if(field == null) {
field = createEntityField(propertyId, bean, propertyType);
}
if(field == null) {
field = createDateField(pid, propertyType);
}
if(field == null) {
field = createPasswordField(pid, crudFieldAnnotation);
}
if(field == null) {
field = createDownloadableField(bean, pid, uiContext, downloadableAnnotation);
}
if(field == null) {
field = super.createField(item, propertyId, uiContext);
}
configureField(field, bean, item, pid, uiContext, propertyType, crudFieldAnnotation, columnAnnotation, joinColumnAnnotation, typeAnnotation);
} catch (SecurityException e) {
throw new RuntimeException(e);
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
} catch (IllegalArgumentException e) {
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
return field;
}
/**
* Creates a field.
* @param container Container to use for getting the item.
* @param itemId item id (Entity/Dto).
* @param propertyId property name.
* @param uiContext the component where the field is presented.
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public Field createField(Container container, Object itemId, Object propertyId, Component uiContext) {
Item item = container.getItem(itemId);
if(item instanceof EntityItem) {
EntityItem entityItem = (EntityItem) item;
item = new BeanItem(entityItem.getPojo());
}
Field field = createField(item, propertyId, uiContext);
field.setBuffered(true);
FieldContainer fieldContainer = (FieldContainer) uiContext;
fieldContainer.addField(field, propertyId, item);
return field;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public Field createEmbeddedTableField(Object propertyId, Object bean, Class<?> propertyType, CrudField crudFieldAnnotation) {
Field field = null;
if(crudFieldAnnotation != null && crudFieldAnnotation.embedded()) {
try {
Dto dto = (Dto) bean;
if(dto.getId() != null) {
bean = (Dto) ContainerFactory.getInstance().getContainer(bean.getClass()).getEntity((Serializable) dto.getId());
}
String getterName;
if(propertyType == boolean.class || propertyType == Boolean.class) {
getterName = "is" + propertyId.toString().substring(0, 1).toUpperCase() + propertyId.toString().substring(1, propertyId.toString().length());
} else {
getterName = "get" + propertyId.toString().substring(0, 1).toUpperCase() + propertyId.toString().substring(1, propertyId.toString().length());
}
Class<?> clazz = (Class)((ParameterizedType)(bean).getClass().getDeclaredField(propertyId.toString()).getGenericType()).getActualTypeArguments()[0];
Collection set = (Collection) bean.getClass().getMethod(getterName).invoke(bean);
field = new EntityTable(clazz, set, new EmbeddedCrudComponent(clazz));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
return field;
}
/**
* Creates a field to specify an Entity value.
* @param propertyId property name.
* @param bean bean to which the field belongs.
* @param propertyType property type.
* @param container container to use to get the Entities (Dtos).
* @return a new field to specify an Entity value.
*/
@SuppressWarnings("rawtypes")
public Field createEntityField(Object propertyId, Object bean, Class<?> propertyType, DefaultHbnContainer<?> container) {
Field field = null;
try {
if(propertyType.equals(Set.class) || propertyType.equals(List.class)) {
Class<?> clazz = (Class)((ParameterizedType)(bean).getClass().getDeclaredField(propertyId.toString()).getGenericType()).getActualTypeArguments()[0];
CrudTable crudTableAnnotation = clazz.getAnnotation(CrudTable.class);
if(crudTableAnnotation == null) {
throw new RuntimeException("Entity class " + clazz.getName() + " doesn't declare a filteringPropertyName (no CrudTable annotation present).");
}
EntitySetField entitySetField = new EntitySetField(clazz, container);
field = entitySetField;
} else if(Dto.class.isAssignableFrom(propertyType)) {
CrudTable crudTableAnnotation = propertyType.getAnnotation(CrudTable.class);
if(crudTableAnnotation == null) {
throw new RuntimeException("Entity class " + propertyType.getName() + " doesn't declare a string representation (no CrudTable annotation present).");
}
EntityField entityField = new EntityField(propertyType, container);
field = entityField;
}
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
}
return field;
}
/**
* Creates a field to specify an Entity value.
* @param propertyId property name.
* @param bean bean to which the field belongs.
* @param propertyType property type.
* @return a new field to specify an Entity value.
*/
public Field createEntityField(Object propertyId, Object bean, Class<?> propertyType) {
return createEntityField(propertyId, bean, propertyType, null);
}
/**
* Creates a date field.
* @param pid property name.
* @param propertyType property type.
* @return a new field to select a date.
*/
public Field createDateField(String pid, Class<?> propertyType) {
Field field = null;
if(Date.class.isAssignableFrom(propertyType)) {
DateField dateField = new DateField();
dateField.setResolution(DateField.RESOLUTION_DAY);
dateField.setDateFormat(Utils.getDateFormatPattern());
field = dateField;
}
return field;
}
/**
* Creates an inline date field.
* @param pid property name.
* @param propertyType property type.
* @return a new field to select a date.
*/
public Field createInlineDateField(String pid, Class<?> propertyType) {
Field field = null;
if(Date.class.isAssignableFrom(propertyType)) {
InlineDateField dateField = new InlineDateField();
dateField.setResolution(InlineDateField.RESOLUTION_DAY);
dateField.setDateFormat(Utils.getDateFormatPattern());
field = dateField;
}
return field;
}
/**
* Checks if the CrudField annotation is present and have "password=true" to create a password field.
* @param pid property name.
* @param crudFieldAnnotation CrudField annotation if present.
* @return a new password field if crudFieldAnnotation is present and is configured to be a password, null otherwise.
*/
public Field createPasswordField(String pid, CrudField crudFieldAnnotation) {
Field field = null;
if(crudFieldAnnotation != null && crudFieldAnnotation.isPassword()) {
field = new PasswordField();
}
return field;
}
/**
* Creates a field to download/upload a file.
* @param bean bean to which the field belongs.
* @param pid property name.
* @param uiContext the component where the field is presented.
* @param downloadableAnnotation
* @return a new download/upload field for the specified property.
* @throws SecurityException
* @throws NoSuchMethodException
*/
public Field createDownloadableField(final Object bean, final String pid, Component uiContext, final Downloadable downloadableAnnotation) throws SecurityException, NoSuchMethodException {
DownloadField field = null;
if(downloadableAnnotation != null) {
String capitalizedFileNameField = downloadableAnnotation.propertyFileName().substring(0, 1).toUpperCase() + downloadableAnnotation.propertyFileName().substring(1, downloadableAnnotation.propertyFileName().length());
final Method setFileNameMethod = bean.getClass().getMethod("set" + capitalizedFileNameField, String.class);
final Method getFileNameMethod = bean.getClass().getMethod("get" + capitalizedFileNameField, (Class<?>[]) null);
field = new DownloadField(EnterpriseApplication.getInstance()) {
private static final long serialVersionUID = 1L;
@Override
public void uploadFinishedEvent(FinishedEvent event) {
super.uploadFinishedEvent(event);
try {
setFileNameMethod.invoke(bean, event.getFilename());
} catch (IllegalArgumentException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
}
@Override
public String getFileName() {
String name = "file";
try {
name = getFileNameMethod.invoke(bean, (Object[]) null) + downloadableAnnotation.fileNameSufix();
} catch (IllegalArgumentException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
return name;
}
};
}
return field;
}
/**
* Adds the necessary validators to the specified field.
* @param field field wich the validators will be added.
* @param bean bean to which the field belongs.
* @param item item id (Entity/Dto).
* @param pid property name.
* @param uiContext the component where the field is presented.
* @param propertyType property type.
* @param crudFieldAnnotation CrudField annotation if present.
*/
public void addValidators(Field field, Object bean, Item item, String pid, Component uiContext, Class<?> propertyType, CrudField crudFieldAnnotation) {
if(crudFieldAnnotation != null && crudFieldAnnotation.isEmail()) {
field.addValidator(new EmailValidator(Constants.uiInvalidEmail));
}
if(Integer.class.isAssignableFrom(propertyType)) {
field.addValidator(new IntegerValidator(Constants.uiInvalidIntegerValue));
} else if(Long.class.isAssignableFrom(propertyType)) {
field.addValidator(new LongValidator(Constants.uiInvalidLongValue));
} else if(Double.class.isAssignableFrom(propertyType) || BigDecimal.class.isAssignableFrom(propertyType)) {
field.addValidator(new DoubleValidator(Constants.uiInvalidDoubleValue));
}
}
/**
* Returns the required error for the specified field.
* @param field field to configure.
* @return error string for the specified field.
*/
public String getRequiredError(Field field) {
return Constants.uiRequiredField + ": " + field.getCaption() + ".";
}
/**
* Returns the field caption for the specified property. Gets the error string from properties file.
* @param propertyId property name.
* @param type property type.
* @return field caption.
*/
public static String getFieldCaption(Object propertyId, Class<?> type) {
String typeName = type.getSimpleName();
String nameFromFile = Utils.getPropertyLabel(typeName, propertyId);
return nameFromFile.isEmpty() ? DefaultFieldFactory.createCaptionByPropertyId(propertyId) : nameFromFile;
}
/**
* If the field is required, sets the required error string.
* @param field field to configure.
* @param columnAnnotation JPA Column annotation, if present.
* @param joinColumnAnnotation JoinColumn annotation, if present.
*/
public void checkRequired(Field field, CrudField crudFieldAnnotation, Column columnAnnotation, JoinColumn joinColumnAnnotation) {
if(crudFieldAnnotation != null && crudFieldAnnotation.forceRequired()) {
field.setRequired(true);
} else if((columnAnnotation != null && !columnAnnotation.nullable()) || (joinColumnAnnotation != null && !joinColumnAnnotation.nullable())) {
field.setRequired(true);
}
if(field.isRequired()) {
field.setRequiredError(getRequiredError(field));
}
}
/**
* If the Column annotation has a "length" attribute, adds a validator to check for the maximum allowed length.
* @param field
* @param columnAnnotation
*/
public void checkLength(Field field, Column columnAnnotation, Type typeAnnotation) {
if(typeAnnotation != null && !"text".equals(typeAnnotation.type()))
if(AbstractTextField.class.isAssignableFrom(field.getClass())) {
if(columnAnnotation != null) {
field.addValidator(new StringLengthValidator(Constants.uiMaxLengthExceeded(columnAnnotation.length()), 0, columnAnnotation.length(), true));
}
}
}
/**
* Configures the null representation for the field, if necessary.
* @param field field to configure.
*/
public void checkNullRepresentation(Field field) {
if(AbstractTextField.class.isAssignableFrom(field.getClass())) {
((AbstractTextField) field).setNullRepresentation("");
((AbstractTextField) field).setNullSettingAllowed(true);
((AbstractTextField) field).setImmediate(true);
}
}
}