package org.nocket.component.form; import gengui.util.SevereGUIException; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.Map; import org.apache.wicket.Component; import org.apache.wicket.MarkupContainer; import org.apache.wicket.behavior.Behavior; import org.apache.wicket.markup.html.form.Form; import org.apache.wicket.markup.html.form.FormComponent; import org.apache.wicket.markup.transformer.AbstractTransformerBehavior; import org.apache.wicket.model.IChainingModel; import org.apache.wicket.model.IModel; import org.apache.wicket.util.visit.IVisit; import org.apache.wicket.util.visit.IVisitor; import org.nocket.component.form.beans.BeanInfoPropertyDescriptor; import org.nocket.component.form.beans.BeanInfoResolver; import org.nocket.component.form.behaviors.ValidationStyleBehavior; import org.nocket.component.form.behaviors.ValidationStyleGroupBehavior; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Wicket Form with support for bean validation JSR 302 API. * * @author blaz02 */ public class BeanValidationForm<T> extends Form<T> { private static final long serialVersionUID = 1L; private static final Logger log = LoggerFactory.getLogger(BeanValidationForm.class); private boolean validatorsAdded = false; private boolean editMode; private Class<? extends AbstractTransformerBehavior> validationStyleBehaviorClass; private Class<? extends AbstractTransformerBehavior> validationStyleGroupBehaviorClass; /** * Constructor for the form. Entity is a simple POJO with annotations * following JSR 303 Bean Validation standard. Entity and its nested POJOs * cannot be null. Internally entity will be put * {@link DMDCompundPropertyModel}. * * @param id * wicket's id of the form. * @param entity * instance of the entity. * * @throws IllegalStateException * if the entity is null or if any nested POJOs is null. * */ public BeanValidationForm(String id, T entity) { this(id, new DMDCompoundPropertyModel<T>(entity)); } /** * Constructor for the form. Entity is a simple POJO with annotations * following JSR 303 Bean Validation standard. Entity and its nested POJOs * cannot be null. Internally entity will be put * {@link DMDCompundPropertyModel}. * * @param id * wicket's id of the form. * @param entity * instance of the entity. * * @throws IllegalStateException * if the entity is null or if any nested POJOs is null. * */ public BeanValidationForm(String id, IModel<T> model) { this(id, new DMDCompoundPropertyModel<T>(model)); } public void addWithValidation(final Component... childs) { super.add(childs); } /** * Constructor for the form. The model and the object within it cannot be * null. * * @param id * Wicket's id of the form. * @param entity * Instance of {@link DMDCompundPropertyModel}. * * @throws IllegalStateException * if the model object entity is null or if any nested POJOs is * null. * */ public BeanValidationForm(String id, DMDCompoundPropertyModel<T> model) { super(id, model); if (model == null) throw new IllegalStateException("Model cannot be null."); } public boolean isEditMode() { return editMode; } public void setEditMode(boolean editMode) { this.editMode = editMode; } @Override protected void onBeforeRender() { super.onBeforeRender(); // Add validators to the controls before first rendering if (!validatorsAdded) { // Resolve properties of an entity. final Map<String, BeanInfoPropertyDescriptor> entityProperties = new BeanInfoResolver(getModelObject() .getClass()).resolveProperties(); // Visit children of the form and add validator to FormComponents // if the are bound to the properties of an entity visitChildren(new IVisitor<Component, Component>() { @Override public void component(Component component, IVisit<Component> visit) { StringBuilder msg = new StringBuilder(192); if (component instanceof FormComponent) { FormComponent c = (FormComponent) component; final String path = getComponentPropertyPath(c); msg.append("Visiting: ").append(getValidatorPropertyPath(c)).append(". Property path is: ") .append(path); if (path == null) { if (log.isDebugEnabled()) { msg.append(". Continue with next component."); log.debug(msg.toString()); } return; } final BeanInfoPropertyDescriptor desc = entityProperties.get(path); if (desc != null) { if (log.isDebugEnabled()) { msg.append(". Descriptor found."); log.debug(msg.toString()); } // c.add(new JSR303Validator<T>(getValidatorPropertyPath(c), getValidatorPropertyPrompt(c), // desc.getEntityClass(), // editMode ? c.getModel() : null)); c.add(newValidationStyleBehavior(!belongsToGroupBorder(c))); } else { msg.append(". Descriptor NOT found."); log.warn(msg.toString()); } } else if (component instanceof ComponentGroup) { component.add(newValidationStyleGroupBehavior()); } } }); validatorsAdded = true; } } protected String getValidatorPropertyPath(FormComponent c) { return c.getId(); } /** * Methods return True, if the specified component has a parent of Class * ComponentGroup. * * @param component * * @return true if the specified component has a parent of class * {@link ComponentGroup}. */ private boolean belongsToGroupBorder(Component component) { for (Component current = component; current != null;) { final MarkupContainer parent = current.getParent(); if (parent != null && parent instanceof ComponentGroup) { return true; } current = parent; } return false; } /** * Calculates component property path in the same manner as * {@link BeanInfoResolver} does. * * @param component * reference to the FormComponent. * * @return string with a full class name and a property name i.e.: * "my.example.package.MyPojo.propertyName". */ protected String getComponentPropertyPath(FormComponent component) { for (IModel<?> m = component.getDefaultModel(); m != null && m instanceof IChainingModel<?>;) { if (m instanceof DMDCompoundPropertyModel<?>) { return ((DMDCompoundPropertyModel<?>) m).getTargetClass().getName() + "." + component.getId(); } m = ((IChainingModel<?>) m).getChainedModel(); } return null; } protected String getValidatorPropertyPrompt(FormComponent c) { return getComponentPropertyPath(c); } /** * Factory method for the Behavior, which will be added to every * FormComponent of the form. Default implementation returns * * @param showInline * * @return */ protected Behavior newValidationStyleBehavior(boolean showInline) { Behavior result; if (validationStyleBehaviorClass == null) { result = new ValidationStyleBehavior(showInline); } else { result = invokeBehavior(validationStyleBehaviorClass, showInline); } return result; } private Behavior invokeBehavior(Class<? extends AbstractTransformerBehavior> clazz, boolean showInline) { try { Constructor<? extends AbstractTransformerBehavior> constructor = clazz.getConstructor(boolean.class); return constructor.newInstance(showInline); } catch (IllegalArgumentException e) { throw new SevereGUIException(e); } catch (InstantiationException e) { throw new SevereGUIException(e); } catch (IllegalAccessException e) { throw new SevereGUIException(e); } catch (InvocationTargetException e) { throw new SevereGUIException(e); } catch (SecurityException e) { throw new SevereGUIException(e); } catch (NoSuchMethodException e) { throw new SevereGUIException(e); } } /** * Factory method for the Behavior, which will be added to every * FormComponent of the form. Default implementation returns * * @param showInline * * @return */ protected Behavior newValidationStyleGroupBehavior() { Behavior result; if (validationStyleGroupBehaviorClass == null) { result = new ValidationStyleGroupBehavior(); } else { result = invokeBehavior(validationStyleGroupBehaviorClass); } return result; } private AbstractTransformerBehavior invokeBehavior(Class<? extends AbstractTransformerBehavior> clazz) { try { return clazz.newInstance(); } catch (IllegalArgumentException e) { throw new SevereGUIException(e); } catch (InstantiationException e) { throw new SevereGUIException(e); } catch (IllegalAccessException e) { throw new SevereGUIException(e); } catch (SecurityException e) { throw new SevereGUIException(e); } } public Class<? extends AbstractTransformerBehavior> getValidationStyleBehaviorClass() { return validationStyleBehaviorClass; } public void setValidationStyleBehaviorClass( Class<? extends AbstractTransformerBehavior> validationStyleBehaviorClass) { this.validationStyleBehaviorClass = validationStyleBehaviorClass; } public Class<? extends AbstractTransformerBehavior> getValidationStyleGroupBehaviorClass() { return validationStyleGroupBehaviorClass; } public void setValidationStyleGroupBehaviorClass( Class<? extends AbstractTransformerBehavior> validationStyleGroupBehaviorClass) { this.validationStyleGroupBehaviorClass = validationStyleGroupBehaviorClass; } }