Package org.chwf.filter

A package that manages string-to-data conversions for JavaBeans.

See:
          Description

Class Summary
BeanFilter The BeanFilter class is the abstract superclass of all bean filters.
BeanWrapper The BeanWrapper class is a stateful business object wrapper that manages string-to-object value conversions and property change events for a single business object.
GenericBeanFilter Generic default filter created through introspection.
GenericValidator Generic validator.
PropertyFilter An abstract superclass of property filters.
Validator The Validator class is the abstract superclass of all validators.
 

Exception Summary
FilterException Superclass for exceptions in the filter package.
InitializationException Exception thrown during filter initialization.
OperationException Exception that wraps exceptions from underlying methods.
PropertyNotFoundException Exception thrown when a property is not found.
PropertyNotReadableException Exception thrown when a property is not readable.
PropertyNotWriteableException Exception thrown when a property is not writeable.
ValidationException Exception thrown for validation errors.
 

Package org.chwf.filter Description

A package that manages string-to-data conversions for JavaBeans. The filter library is an abstraction layer that is responsible for:

It was necessary to develop the filter library because none of the existing structures in the Java language adequately address these issues:

Another major open source framework that addresses this issue (the org.apache.commons.beanutils API) only deals with conversions on a per field basis and doesn't allow any customization specific to a particular business object class. In the author's opinion isn't good enough either. For example, what about the case where a double value represents a percentage in one object but a currency value in another?

The filter library using a syntax similar to both reflection and the java.beans classes. The heart of the package is the abstract BeanFilter class. This class is analogous to the java.beans.BeanInfo classes. Unlike those classes, however, each BeanFilter is a bean-specific singleton. All of the filter's properties are immutable and all of its methods are stateless, making it thread safe.

Finding Bean Filters

The abstract BeanFilter class has factory methods for retrieving a BeanFilter for a specific Java Bean:

  BeanFilter filter = BeanFilter.findFilter(Object | Class | String);

The parameter for the findFilter method can be an object, its Class or its fully qualified class name as a string. This method retrieves the BeanFilter in one of three ways:

String Conversions

Once located, the bean filter can be used to get/set properties object using string values.

  BeanFilter filter = BeanFilter.findFilter(account);
  filter.set(account, "name", "John Smith");
    // account.setName("John Smith");
  filter.set(account, "balance", "1234.56");
    // account.setBalance(1234.56);
  filter.set(account, "startDate", "10/11/2001");
    // account.setStartDate(<10/11/2001>);

Object Meta-Data

The BeanFilter object holds a collections of PropertyFilter objects. The get() and set() methods of the BeanFilter delegate to the appropriate PropertyFilter. The property filters can be accessed directly through the getPropertyFilters() method. This allows dynamic discovery of the properties of the bean:

    BeanFilter filter = BeanFilter.findFilter(account);

    PropertyFilter[] propertyFilters = filter.getPropertyFilters();
    for (int i=0; i<propertyFilters.length; i++) {
      String name = propertyFilters[i].getName();
      // Do something with that property
    }

In addition, the PropertyFilter stores property meta-data. This meta-data can be useful during user interface generation, especially if the UI is in a different language than Java, such as HTML/JavaScript.

  BeanFilter filter = BeanFilter.findFilter(account);
  PropertyFilter propertyFilter = filter.getPropertyFilter(property);
  String attributeValue = propertyFilter.getAttribute(attribute);

The text filter API does not dictate any particular structure to property meta-data, but other layers of the Chrysalis framework uses certain values:

label  The label for the field. Defaults to the field name.
datatype  The field's data type.
readonly  Whether the field is read-only. This is useful for calculated fields.
required  Whether the field is required.
max  The field's maximum value (numeric types only). This value is included in the allowed range.
min  The field's minimum value (numeric types only). This value is included in the allowed range.
maxlength  The field's maximum length (string types only). This value is included in the allowed range.
minlength  The field's minimum length (string types only). This value is included in the allowed range.
scale  The number of digits after the decimal place (decimal types only).
options  A map containing a list of options for constrained values. The keys in this map should be the valid values for the property, and the values in the map should be human-readable interpretations of these values. This attribute is intended to generate drop-down lists or select lists.

A few of the above attribute values can be deduced from the business object's structure.

Java Bean Support

The text filter API places very few restrictions on the structures of the business objects it uses. The only firm requirement is that they have properties encapsulated in get/set methods.

Metadata information can be embedded in the chrysalis.xml configuration file for the JavaBean package.

  <config>
    <class name="Item">
      <property name="name" maxlength="20" />
      <property name="stock" min="0" max="1000" />
    </class>
    <!-- Metadata for other beans ... -->
  </config>

The following property attributes have default values if not specified by the configuration:

label  Defaults to the field name.
readonly  Defaults to true unless (a) the property has setter method and (b) the property type has Converter (that is, it is a simple type).
datatype  Defaults to the property's Java type, or its object wrapper type if primitive. The default uses the type's short class name, not the fully qualified name. For example, strings are "String", java.util.Date is "Date" and int is "Integer".

Business objects that do not following these conventions can have their meta-data defined through custom filters.

Filter Customization

Sometimes the default GenericBeanFilter is not accurate enough to describe the business object and its string conversions. The filter library also supports the definition of custom filters. The custom filter must be in the same package as the bean, and its class name must be beanClass + "Filter". If this class exists, the findFilter() method returns an instance of this class rather than a dynamically generated filter. The filter class can be defined in two ways:

The first technique is generally preferable. The second technique is useful if some of a beans public properties and methods must be hidden from the user interface. For the most part, the custom filter class need only define its constructor. For example, the filter for the example.Account class could be defined as:

package example;
import org.chwf.filter.*;
import java.text.SimpleDateFormat;

public class AccountFilter extends GenericBeanFilter {
  public AccountFilter() {
    super(Account.class);

    // startDate property filter as an anonymous inner class:
    PropertyFilter sdFilter = new PropertyFilter("startDate", java.util.Date.class) {

      public String get(Object object) {
        java.util.Date date = ((Account) object).getStartDate();
        if (date == null) {
          return null;
        } else {
          SimpleDateFormat df = new SimpleDateFormat("mm/dd/yyyy");
          return df.format(date);
        }
      }

      public Object getPropertyAsObject(Object object) {
        return ((Account) object).getStartDate();
      }

      public void set(Object object, String value) {
        try {
          SimpleDateFormat df = new SimpleDateFormat("mm/dd/yyyy");
          ((Account) object).setStartDate(df.parse(value));
        } catch (java.text.ParseException ex) {
          throw new IllegalArgumentException(ex.getMessage());
        }
      }
    };
    putPropertyFilter("startDate", sdFilter);

    // Initialize property attributes from constant values:
    initializePropertyAttributes();
  }
}

Any property filters defined in the constructor of the custom filter class replaces the generic filters created in the GenericBeanFilter constructor. As noted in the description of the GenericBeanFilter class, the initializePropertyAttributes() method is not automatically called in the GenericBeanFilter constructor. It should be called manually in the custom filter subclass after all property filters have been put into the bean filter. Changes to property attributes can be made after the call to initializePropertyAttributes()

  initializePropertyAttributes();
  putPropertyAttribute("balance", "datatype", "Currency");

Performance notes: By eliminating reflection, custom filters have slightly better performance than generic filters. The performance gain may seem significant, in the neighborhood of 33%, but when combined with the other operations that the application is performing (managing remote calls, load and save operations to the database), the effects of this gain will be negligible. Therefore, the developer should only define custom filters when absolutely necessary, to override behavior that is not correctly defined through reflection.

Property Change Events

The BeanWrapper class is a stateful business object wrapper that manages string-to-object value conversions and property change events for a single business object. It simplifies BeanFilter methods by eliminating the object parameter, but it is not thread-safe. The BeanWrapper is appropriate for use in non-multi-threaded environments like Java GUIs:

  BeanWrapper wrapper = new BeanWrapper(account);
  wrapper.set("name", "John Smith");
    // account.setName("John Smith");
  wrapper.set("balance", "1234.56");
    // account.setBalance(1234.56);
  wrapper.set("startDate", "10/11/2001");
    // account.setStartDate(<10/11/2001>);

The BeanWrapper class also allows registration of PropertyChangeListener objects to handle PropertyChangeEvents. This means that the business objects themselves do not need to be encumbered with such GUI specific code. These property change events are only generated when the property is changed through the BeanWrapper, using its set() and setAll() methods.

  BeanWrapper wrapper = new BeanWrapper(account);
  PropertyChangeListener bcl = new BalanceChangeListener();
  wrapper.addPropertyChangeListen("balance", bcl);
  // listener is notified when balance is changed via the wrapper

Internationalization

The bean filter API leverages the internationalization features of the Converter class that it uses for string conversions. To specify the user locale:

  UserLocale.setLocale(locale);

In addition, you can retrieve localized attribute values (such as for property labels) using the PropertyFilter.getLocalizedAttribute() method.



Copyright © 2002-2004, Paul Strack. All Rights Reserved.