My favorites | Sign in
Project Home Downloads Wiki Issues Source
Search
for
ModuleBean  
Describes the Bean module.
Updated Sep 29, 2010 by ubon...@gmail.com

Installation

Add the gwtoolbox-bean-XX.jar to your classpath and add the following line to your GWT module file:

<inherits name="org.gwtoolbox.bean.Bean"/>

Quick Start

Using this module is as simple as following the following steps:

1. Write your bean class

public class Person {

   private String name;

   private String email;

   public Person() {
      // default constructor is required (or not constructor at all)
   }

   public String getName() {
      return name;
   }

   public void setName(String name) {
      this.name = name;
   }

   public String getEmail() {
      return email;
   }

   public void setEmail(String email) {
      this.email = email;
   }
}

2. Annotate the bean with the @Bean annotation. This annotation will be used by the module to recognize and process this class as a bean:

@Bean
public class Person {
   ...
}

3. Optionally (yet, recommended), annotate the properties (either the getters or the fields themselves) with JSR-303 constraint annotations (later on we'll see how to create custom constraints):

@Bean
public class Person {

   @NotNull
   private String name;

   // The @Email annotation is not a standard JSR-303 annotation, but does come out of the box with gwtoolbox-bean module.
   @Email 
   private String email;

   ...
}

4. It is highly recommended (although not mandatory) to define a configuration interface which will be used to configure how the bean module will do its processing:

@BeanScan(packages = "my.package.*", resolver = PackageResolver.REGEXP)
@AnnotationBasedBeanSelector
public interface MyBeanInfoRegistryConfiguration extends BeanInfoRegistryConfiguration {}
  • The @BeanScan tells the module what packages in the code base to scan for beans.
  • The @AnnotationBasedBeanSelector indicates that only classes with @Bean annotation should be treated as beans (It is also possible to scan for classes annotated with other annotations by setting the "beanAnnotationType" attribute of this annotation. This can be useful, for example, when using JPA as the @Entity annotation can also be used).
  • Note that this interface must extends the BeanInfoRegistryConfiguration interface.

5. You are now ready to actually use the bean support:

  • Getting the BeanInfo for a bean:
  • ...
    
    BeanInfo beanInfo = BeanInfoRegistry.get().getBeanInfo(Person.class);
    
    ...
  • Getting a property descriptor for the "name" property of the bean:
  • ...
    BeanInfo beanInfo = BeanInfoRegistry.get().getBeanInfo(Person.class);
    PropertyDescriptor descriptor = beanInfo.getPropertyDescriptor("name");
    ...
  • Setting the "name" property value of an existing Person instance:
  • ...
    Person person = new Person();
    BeanInfo beanInfo = BeanInfoRegistry.get().getBeanInfo(Person.class);
    PropertyDescriptor descriptor = beanInfo.getPropertyDescriptor("name");
    descriptor.setValue(person, "John");
    ...
  • Getting the "name" property value of an existing Person instance:
  • ...
    Person person = new Person();
    person.setName("John");
    BeanInfo beanInfo = BeanInfoRegistry.get().getBeanInfo(Person.class);
    PropertyDescriptor descriptor = beanInfo.getPropertyDescriptor("name");
    String name = descriptor.getValue(person);
    assert name.equals("John");
    ...
  • Setting a nested property of the Person instance (assuming a Person has an "address" property of type Address which is also marked as a bean:
  • Person person = new Person();
    BeanInfo beanInfo = BeanInfoRegistry.get().getBeanInfo(Person.class);
    PropertyDescriptor descriptor = beanInfo.getPropertyDescriptor("name");
    descriptor.getPathValueProxy("address.city").set(person, "Amsterdam");

NOTE: if The "address" property of the Person is set to null setting the nested property will create a new Address instance and set it on the person instance.

  • Getting a nested property of the Person instance:
  • Person person = new Person();
    Address address = new Address();
    addresss.setCity("Amsterdam");
    person.setAddress(address);
    
    BeanInfo beanInfo = BeanInfoRegistry.get().getBeanInfo(Person.class);
    PropertyDescriptor descriptor = beanInfo.getPropertyDescriptor("name");
    String city = (String) descriptor.getPathValueProxy("address.city").get(person);
    assert "Amsterdam".equals(city);
  • Validating a Person instance (based on JSR-303 annotations)
  • Person person = new Person();
    BeanInfo beanInfo = BeanInfoRegistry.get().getBeanInfo(Person.class);
    BeanValidator validator = beanInfo.getValidator();
    Set<ConstraintViolation> violations = validator.validate(person);

Type System

During the processing (gwt compile time) the bean module analyzes the beans and generates meta-data information over the data types of the beans and the properties. This information is accessible via the {{Type}} class which can be retrieved for both the Beans and the properties:

BeanInfo beanInfo = BeanInfoRegistry.get().getBeanInfo(Person.class);
Type beanType = beanInfo.getType();

PropertyDescriptor desriptor = beanInfo.getPropertyDescriptor("name");
Type nameType = descriptor.getType();

Here is the information you can get from the Type class:

  • the type class
  • whether it is a Number
  • whether it is a Collection
  • whether it is an array
  • whether it is a bean
  • whether it is a primitive type
  • whether it is a Map
  • whether it is a Date
  • whether it is an Enum
  • in case of a collection or an array data structure, you can also get the Type of the element/items stored in it (for collections, this is based on the generic type the collections are defined with)

Validation

The JSR-303 validation support is built to be as much as compatible with the specifications as possible (full compatibility is not possible, mainly due to the runtime restrictions of GWT).

Validation Constraints

One can develop additional validation constraints which can be used with your beans. For full understanding on how Bean Validation works please refer to the specifications. But in short, in order to add constraints, you'll need to define a constraint annotation and its associated validator. The following example shows how the @Email constraint is defined:

public class EmailConstraintValidator extends AbstractConstraintValidator<Email, String> {

    private final static String EMAIL_PATTERN = "^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,4}$";

    public boolean isValid(String value, ConstraintValidatorContext context) {
        return value == null || value.matches(EMAIL_PATTERN);
    }
}

@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {EmailConstraintValidator.class})
public @interface Email {

	/**
	 * @return The error message template.
	 */
	String message() default "{org.gwtoolbox.bean.client.validation.constraint.Email.message}";

	/**
	 * @return The groups the constraint belongs to.
	 */
	Class<?>[] groups() default { };

	/**
	 * @return The payload associated to the constraint
	 */
	Class<? extends Payload>[] payload() default {};

}

NOTE: The ConstraintValidator interface in this module differs slightly from the one defined by the JSR specifications. This is to enable initialization of the validator at runtime using a Map<String, Object> rather than an annotation (GWT doesn't provide access to annotations at runtime).

Validation Messages

As shown above, the error message for a constraint is defined as an attribute on the constraint annotation. It can be either the message itself, or a key to a resource bundle which will be resolved at runtime. In the later case, the key needs to be "marked" within curly brackets (as shown above). There is already a default resource bundle which defines messages to all the constraints that come out of the box with the module, but it is often the case where you'd want to customize these messages. You can do that by simple defining an interface which extends ValidationMessages. This interface also extends the standard GWT Constants interface and you can choose to either override the part or all of the default methods of the base class (that is, if you'd like to only customize some of the default messages). In your custom interface, you'll also be able to add additional keys/methods for the custom constraints you define.

The values of the keys are templates by themselves. They can consist of plain text, but also of named parameter placeholders which are marked by curly brackets. These parameters should refer to the attributes that are defined by the relevant constraint annotation. So for example, a message for the @Min annotation (which accepts a "value" attribute of type int) can be formated as follows:

The value must be greater than {value}

At runtime, this template will be processed and the value you define in the @Min annotation will replace the {value} parameter. For example, if a @Min(0) annotation is placed over a property which at runtime holds a negative value, the following message will be resolved:

The value must be greater than 0

Extra MetaData

There are two additional meta-data annotations you can place on bean classes and their propertids:

  • @DispalyName - Use it to provide a readable/displayble name for the bean/property
  • @Description - Add a description that describes the meaning/use of the bean/property

These two properties can be used in UI construct such as Forms to display labels and tooltips associated with the properties.

The module enables you to define additional custom properties which you can place on the properties of a bean. Such properties can be used for different purposes depending on your needs. For example, One can define a @Format attributes which will accepts a string format (either a date format or a number format) which can be used to define how a Date or Number editor should display the value of the field. Since annotations are not available at runtime for GWT application, these annotations are translated to an Attributes class. The Attributes can be type safe, so basically in order to properly support the @Format annotation, a FormatAttributes class can be creates which extends AbstractAttributes. Then just annotate the @Format annotation definition with @AttributeClass(FormatAttributes.class). This will let the module processor know that it should translate the @Format annotation to a FormatAttributes class. You can then retrieve these attribute from the property descriptor. For example:

@Format Annotation

@Target({ ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@AttributesClass(FormatAttributes.class)
public @interface Format {

    String value();

}
FormatAttributes.class
public class FormatAttributes extends AbstractAttributes {

    public String value() {
        return getString("value");
    }

}

Using FormatAttributs

...
String format = "dd-MM-yyyy"; //default format
PropertyDescriptor descriptor = beanInfo.getPropertyDescriptor("date");
if (descriptor.hasAttributes(FormatAttributes.class)) {
   String format = descriptor.getAttributes(FormatAttributes.class).value(); 
}
...

TODO

  • Constraint composition
  • GroupSequence support

Sign in to add a comment
Powered by Google Project Hosting