WebWork has one of the most advanced Routine type conversion abilities in any web-based framework in any Java languagethe framework is transparent. Generally, all you don't need to do anything to take advantage of it, other than name your HTML inputs (is ensure that HTML inputs have names that can be used in OGNL expressions. (HTML inputs are form elements and other GET/POST parameters.) names that are valid OGNL expressions.
A Simple Example
Wiki Markup |
---|
{snippet:id=javadoc|javadoc=true|url=com.opensymphony.xwork.util.XWorkConverter} |
Note | ||
---|---|---|
|
WebWork ships with a helper base class that makes converting to and from Strings very easy. The class is com.opensymphony.webwork.util.WebWorkTypeConverter. This class makes it very easy for you to write type converters that handle converting objects to Strings as well as from Strings. From the JavaDocs for this class:
Wiki Markup |
---|
{snippet:id=javadoc|javadoc=true|url=com.opensymphony.webwork.util.WebWorkTypeConverter} |
...
Built in Type Conversion Support
...
Type Conversion is implemented by XWork.
...
- Enumerations
- BigDecimal and BigInteger
Relationship to Parameter Names
The best way to take advantage of WebWork's type conversion is to utilize complete objects (ideally your domain objects directly), rather than submitting form values on to intermediate primitives and strings in your action and then converting those values to full objects in the execute() method. Some tips for achieving this are:
There is no need to capture form values using intermediate Strings and primitives. Instead, the framework can read from and write to properties of objects addressed via OGNL expressions and perform the appropriate type conversion for you.
Here are some tips for leveraging the framework's type conversion capabilities:
- Use OGNL expressions - the framework Use complex OGNL expressions - WebWork will automatically take care of creating the actual objects for you.
- Use JavaBeans! WebWork The framework can only create objects for you if your objects that obey the JavaBean specification and , provide no-arg constructions , as well as and include getters and setters where appropriate.
- Remember that person.name will call getPerson().setName(), but if you are expecting WebWork to create . If the framework creates the Person object for you, it remember that a
setPerson
() method must also exist. Wiki Markup For lists and maps, use index notation, such as _people\[0\].name_ or _friends\['patrick'\].name_. Often these HTML form elements are being rendered inside a loop, so you can use the iterator tag's status attribute if you're using [JSP Tags] or the $\{foo_index\} special property if you're using [FreeMarker Tags].
- The framework will not instantiate an object if an instance already exists. The PrepareInterceptor or action's constructor can be used to create target objects before type conversion.
- For lists and maps, use index notation, such as people[0].name or friends['patrick'].name. Often these HTML form elements are being rendered inside a loop. For JSP Tags, use the iterator tag's status attribute. For FreeMarker Tags, use the special property ${foo_index}[].
- For multiple select boxes, it isn't possible to use index notation to name each individual itemFOr multiple select boxes, you obviously can't name each individual item using index notation. Instead, name your element simply people.name and WebWork the framework will understand that it should create a new Person object for each selected item and set it's its name accordingly.
Advanced Type Conversion
WebWork also has some very advanced, yet easy-to-use, type conversion features. Null property handling will automatically create objects where null references are found. Collection and map support provides intelligent null handling and type conversion for Java Collections. Type conversion error handling provides an easy way to distinguish the difference between an input validation problem from an input type conversion problem.
Null Property Handling
...
Creating a Type Converter
Create a type converter by extending StrutsTypeConverter. The Converter's role is to convert a String to an Object and an Object to a String.
...
To allow Struts to recognize that a conversion error has occurred, the converter class needs to throw XWorkException or preferably TypeConversionException.
...
Applying a Type Converter to an Action
Create a file called 'ActionClassName-conversion.properties' in the same location of the classpath as the Action class itself resides.
Eg. if the action class name is MyAction, the action-level conversion properties file should be named 'MyAction-conversion.properties'. If the action's package is com.myapp.actions the conversion file should also be in the classpath at /com/myapp/actions/.
Within the conversion file, name the action's property and the Converter to apply to it:
...
Type conversion can also be specified via Annotations within the action.
Applying a Type Converter to a bean or model
When getting or setting the property of a bean, the framework will look for "classname-conversion.properties" in the same location of the classpath as the target bean. This is the same mechanism as used for actions.
Example: A custom converter is required for the Amount property of a Measurement bean. The Measurement class cannot be modified as its located within one of the application's dependencies. The action using Measurement implements ModelDriven<Measurement> so it cannot apply converters to the properties directly.
Solution: The conversion file needs to be in the same location of the classpath as Measurement. Create a directory in your source or resources tree matching the package of Measurement and place the converters file there.
eg. for com.acme.measurements.Measurement, create a file in the application source/resources at /com/acme/measurements/Measurement-conversion.properties:
...
Applying a Type Converter for an application
Application-wide converters can be specified in a file called xwork-conversion.properties located in the root of the classpath.
...
A Simple Example
...
The framework ships with a base helper class that simplifies converting to and from Strings, org.apache.struts2.util.StrutsTypeConverter
. The helper class makes it easy to write type converters that handle converting objects to Strings as well as from Strings.
From the JavaDocs:
...
Advanced Type Conversion
The framework also handles advanced type conversion cases, like null property handling and converting values in Maps and Collections, and type conversion error handling.
Null Property Handling
Null property handling will automatically create objects where null references are found.
...
Collection and Map Support
Collection and Map support provides intelligent null handling and type conversion for Java Collections.
The framework WebWork supports ways to determine discover the object type found for elements in collectionsa collection. This The discover is done made via an ObjectTypeDeterminer. The A default implementation is provided with the framework. The JavaDocs Javadocs explain how map Map and colelction Collection support is determined discovered in the DefaultObjectTypeDeterminer
:
...
.
...
Additionally, you can create your own custom ObjectTypeDeterminer
by implementing the ObjectTypeDeterminer
interface. There is also an optional ObjectTypeDeterminer that utilizes Java 5 generics. See the J2SE 5 Support Annotations page for more information.
Indexing a collection by a property of that collection
It is also possible using webwork to get obtain a unique element of a collection , by passing the value of a given property of that element. By default, the property of the element of the collection is determined in Class-conversion.properties using KeyProperty_xxx=yyy
, where xxx is the property of the bean 'Class' that returns the collection and yyy is the property of the collection element that we want to index on. Here is
For an example with , see the following two classes:
...
...
Then put To enable type conversion, put the instruction KeyProperty_fooCollection=id
in my the MyAction-conversion.properties
file. This would allow to the technique allows use of the idiom fooCollection(someIdValue)
to get obtain the Foo object with value someIdValue
in the Set fooCollection
. For example, fooCollection(22)
would return the Foo object in the fooCollection
collection Collection whose id
property value was 22.
This technique is useful, because it ties a collection element directly to its unique identifier and therefore does not force you . You are not forced to use an index and thus allows you to . You can edit the elements of a collection associated to a bean without any additional codecoding. For example, parameter name fooCollection(22).name
and value Phil
would set name the Foo object Object in the fooCollection
collection Collection whose id
property value was 22 to be Phil.
Webwork The framework automatically converts the type of the parameter sent in to the type of the key property using type conversion.unmigrated-wiki-markup
Unlike Map and List element properties, if fooCollection(22)
does not exist , it will not be created. To do that, use the notation *If you would like it created, use the notation fooCollection.makeNew
\[index
\]
* where index is an integer 0, 1, and so on. Thus, parameter value pairs *fooCollection.makeNew
\[0
\]=Phil
* and *fooCollection.makeNew
\[1
\]=John
* would add two new Foo objects Objects to fooCollection
--
one with name property value Phil
and the other with name property value Bar. Note, however, that in the case of a Set, the equals and hashCode methods should be defined such that they don't only include the id property. That will cause one element of the null id propertied Foos to be removed from the Set.
Sample
Code Block | ||||
---|---|---|---|---|
| ||||
public class MyBean implements Serializable {
private Long id;
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String toString() {
return "MyBean{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
|
Code Block | ||||
---|---|---|---|---|
| ||||
ublic class MyBeanAction implements Action {
private List beanList = new ArrayList();
private Map beanMap = new HashMap();
public List getBeanList() {
return beanList;
}
public void setBeanList(List beanList) {
this.beanList = beanList;
}
public Map getBeanMap() {
return beanMap;
}
public void setBeanMap(Map beanMap) {
this.beanMap = beanMap;
}
public String execute() throws Exception {
return SUCCESS;
}
}
|
Code Block | ||||
---|---|---|---|---|
| ||||
KeyProperty_beanList=id
Element_beanList=com.opensymphony.xwork.util.MyBean
CreateIfNull_beanList=true
Key_beanMap=java.lang.Long
KeyProperty_beanMap=id
Element_beanMap=com.opensymphony.xwork.util.MyBean
|
Type Conversion Error Handling
...
John
. However, in the case of a Set, the equals
and hashCode
methods should be defined such that they don't only include the id
property. Otherwise, one element of the null id
properties Foos to be removed from the Set.
An advanced example for indexed Lists and Maps
Here is the model bean used within the list. The KeyProperty for this bean is the id
attribute.
...
The Action has a beanList
attribute initialized with an empty ArrayList.
...
These conversion.properties
tell the TypeConverter to use MyBean instances as elements of the List.
...
- When submitting this via a form, the
id
value is used as KeyProperty for the MyBean instances in the beanList.
- Notice the () notation! Do not use [] notation, which is for Maps only!
- The value for name will be set to the MyBean instance with this special id.
- The List does not have null values added for unavailable id values. This approach avoids the risk of OutOfMemoryErrors!
...
Type Conversion Error Handling
Type conversion error handling provides a simple way to distinguish between an input validation problem and an input type conversion problem.
...
There are two ways the error reporting can occur:
- globallyGlobally, using the Conversion Error Interceptor
- on On a per-field basis, using the conversion validator
By default, the conversion interceptor is included in webwork struts-default.xml
in the default stack, so if you don't want . To keep conversion errors from reporting globally, you'll need to change the interceptor stack, and add additional validation rules.
Common Problems
Null and Blank Values
Some properties cannot be set to null. Primitives like boolean and int cannot be null. If your action needs to or will accept null or blank values, use the object equivalents Boolean and Integer. Similarly, a blank string "" cannot be set on a primitive. At the time of writing, a blank string also cannot be set on a BigDecimal or BigInteger. Use server-side validation to prevent invalid values from being set on your properties (or handle the conversion errors appropriately).
Interfaces
The framework cannot instantiate an object if it can't determine an appropriate implementation. It recognizes well-known collection interfaces (List, Set, Map, etc) but cannot instantiate MyCustomInterface when all it sees is the interface. In this case, instantiate the target implementation first (eg. in a prepare method) or substitute in an implementation.
Generics and Erasure
The framework will inspect generics to determine the appropriate type for collections and array elements. However, in some cases Erasure can result in base types that cannot be converted (typically Object or Enum).
The following is an example of this problem:
...
Although to the developer the area.setUnits(enumValue) method only accepts a UnitsOfArea enumeration, due to erasure the signature of this method is actually setUnits(java.lang.Enum). The framework does not know that the parameter is a UnitsOfArea and when it attempts to instantiate the Enum an exception is thrown (java.lang.IllegalArgumentException: java.lang.Enum is not an enum type).