com.opensymphony.xwork2.conversion.impl
public class: XWorkConverter [javadoc |
source]
java.lang.Object
com.opensymphony.xwork2.conversion.impl.DefaultTypeConverter
com.opensymphony.xwork2.conversion.impl.XWorkConverter
All Implemented Interfaces:
TypeConverter
Direct Known Subclasses:
AnnotationXWorkConverter
XWorkConverter is a singleton used by many of the Struts 2's Ognl extention points,
such as InstantiatingNullHandler, XWorkListPropertyAccessor etc to do object
conversion.
Type conversion is great for situations where you need to turn a String in to a more complex object. Because the web
is type-agnostic (everything is a string in HTTP), Struts 2's type conversion features are very useful. For instance,
if you were prompting a user to enter in coordinates in the form of a string (such as "3, 22"), you could have
Struts 2 do the conversion both from String to Point and from Point to String.
Using this "point" example, if your action (or another compound object in which you are setting properties on)
has a corresponding ClassName-conversion.properties file, Struts 2 will use the configured type converters for
conversion to and from strings. So turning "3, 22" in to new Point(3, 22) is done by merely adding the following
entry to
ClassName-conversion.properties (Note that the PointConverter should impl the TypeConverter
interface):
point = com.acme.PointConverter
Your type converter should be sure to check what class type it is being requested to convert. Because it is used
for both to and from strings, you will need to split the conversion method in to two parts: one that turns Strings in
to Points, and one that turns Points in to Strings.
After this is done, you can now reference your point (using <s:property value="point"/> in JSP or ${point}
in FreeMarker) and it will be printed as "3, 22" again. As such, if you submit this back to an action, it will be
converted back to a Point once again.
In some situations you may wish to apply a type converter globally. This can be done by editing the file
xwork-conversion.properties in the root of your class path (typically WEB-INF/classes) and providing a
property in the form of the class name of the object you wish to convert on the left hand side and the class name of
the type converter on the right hand side. For example, providing a type converter for all Point objects would mean
adding the following entry:
com.acme.Point = com.acme.PointConverter
Type conversion should not be used as a substitute for i18n. It is not recommended to use this feature to print out
properly formatted dates. Rather, you should use the i18n features of Struts 2 (and consult the JavaDocs for JDK's
MessageFormat object) to see how a properly formatted date should be displayed.
Any error that occurs during type conversion may or may not wish to be reported. For example, reporting that the
input "abc" could not be converted to a number might be important. On the other hand, reporting that an empty string,
"", cannot be converted to a number might not be important - especially in a web environment where it is hard to
distinguish between a user not entering a value vs. entering a blank value.
By default, all conversion errors are reported using the generic i18n key
xwork.default.invalid.fieldvalue,
which you can override (the default text is
Invalid field value for field "xxx", where xxx is the field name)
in your global i18n resource bundle.
However, sometimes you may wish to override this message on a per-field basis. You can do this by adding an i18n
key associated with just your action (Action.properties) using the pattern
invalid.fieldvalue.xxx, where xxx
is the field name.
It is important to know that none of these errors are actually reported directly. Rather, they are added to a map
called
conversionErrors in the ActionContext. There are several ways this map can then be accessed and the
errors can be reported accordingly.
Also see:
- XWorkBasicConverter
- author:
< - a href="mailto:plightbo@gmail.com">Pat Lightbody
- author:
Rainer - Hermanns
- author:
< - a href='mailto:the_mindstorm[at]evolva[dot]ro'>Alexandru Popescu
- author:
tm_jee -
- version:
$ - Date: 2008-03-28 19:48:23 +0100 (Fri, 28 Mar 2008) $ $Id: XWorkConverter.java 1770 2008-03-28 18:48:23Z rainerh $
| Field Summary |
|---|
| protected static final Logger | LOG | |
| public static final String | REPORT_CONVERSION_ERRORS | |
| public static final String | CONVERSION_PROPERTY_FULLNAME | |
| public static final String | CONVERSION_ERROR_PROPERTY_PREFIX | |
| public static final String | CONVERSION_COLLECTION_PREFIX | |
| public static final String | LAST_BEAN_CLASS_ACCESSED | |
| public static final String | LAST_BEAN_PROPERTY_ACCESSED | |
| protected HashMap | mappings | Target class conversion Mappings.
Map>
- Class -> convert to class
- Map
- String -> property name
eg. Element_property, property etc.
- Object -> String to represent properties
eg. value part of
KeyProperty_property=id
-> TypeConverter to represent an Ognl TypeConverter
eg. value part of
property=foo.bar.MyConverter
-> Class to represent a class
eg. value part of
Element_property=foo.bar.MyObject
|
| protected HashSet | noMapping | Unavailable target class conversion mappings, serves as a simple cache. |
| protected HashMap | defaultMappings | Record class and its type converter mapping.
- String - classname as String
- TypeConverter - instance of TypeConverter
|
| protected HashSet | unknownMappings | Record classes that doesn't have conversion mapping defined.
- String -> classname as String
|
| Method from com.opensymphony.xwork2.conversion.impl.XWorkConverter Summary: |
|---|
|
addConverterMapping, buildConverterFilename, convertValue, convertValue, createTypeConverter, getConversionErrorMessage, getConverter, handleConversionException, loadConversionProperties, lookup, lookup, lookupSuper, registerConverter, registerConverterNotFound, setDefaultTypeConverter, setObjectFactory |
| Methods from com.opensymphony.xwork2.conversion.impl.DefaultTypeConverter: |
|---|
|
bigDecValue, bigIntValue, booleanValue, convertValue, convertValue, convertValue, doubleValue, enumValue, getTypeConverter, longValue, stringValue, stringValue |
| Method from com.opensymphony.xwork2.conversion.impl.XWorkConverter Detail: |
void addConverterMapping(Map mapping,
Class clazz) {
try {
String converterFilename = buildConverterFilename(clazz);
InputStream is = FileManager.loadFile(converterFilename, clazz);
if (is != null) {
if (LOG.isDebugEnabled()) {
LOG.debug("processing conversion file ["+converterFilename+"] [class="+clazz+"]");
}
Properties prop = new Properties();
prop.load(is);
Iterator it = prop.entrySet().iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
String key = (String) entry.getKey();
if (mapping.containsKey(key)) {
break;
}
// for keyProperty of Set
if (key.startsWith(DefaultObjectTypeDeterminer.KEY_PROPERTY_PREFIX)
|| key.startsWith(DefaultObjectTypeDeterminer.CREATE_IF_NULL_PREFIX)) {
if (LOG.isDebugEnabled()) {
LOG.debug("\t"+key + ":" + entry.getValue()+"[treated as String]");
}
mapping.put(key, entry.getValue());
}
//for properties of classes
else if (!(key.startsWith(DefaultObjectTypeDeterminer.ELEMENT_PREFIX) ||
key.startsWith(DefaultObjectTypeDeterminer.KEY_PREFIX) ||
key.startsWith(DefaultObjectTypeDeterminer.DEPRECATED_ELEMENT_PREFIX))
) {
TypeConverter _typeConverter = createTypeConverter((String) entry.getValue());
if (LOG.isDebugEnabled()) {
LOG.debug("\t"+key + ":" + entry.getValue()+"[treated as TypeConverter "+_typeConverter+"]");
}
mapping.put(key, _typeConverter);
}
//for keys of Maps
else if (key.startsWith(DefaultObjectTypeDeterminer.KEY_PREFIX)) {
Class converterClass = Thread.currentThread().getContextClassLoader().loadClass((String) entry.getValue());
//check if the converter is a type converter if it is one
//then just put it in the map as is. Otherwise
//put a value in for the type converter of the class
if (converterClass.isAssignableFrom(TypeConverter.class)) {
TypeConverter _typeConverter = createTypeConverter((String) entry.getValue());
if (LOG.isDebugEnabled()) {
LOG.debug("\t"+key + ":" + entry.getValue()+"[treated as TypeConverter "+_typeConverter+"]");
}
mapping.put(key, _typeConverter);
} else {
if (LOG.isDebugEnabled()) {
LOG.debug("\t"+key + ":" + entry.getValue()+"[treated as Class "+converterClass+"]");
}
mapping.put(key, converterClass);
}
}
//elements(values) of maps / lists
else {
Class _c = Thread.currentThread().getContextClassLoader().loadClass((String) entry.getValue());
if (LOG.isDebugEnabled()) {
LOG.debug("\t"+key + ":" + entry.getValue()+"[treated as Class "+_c+"]");
}
mapping.put(key, _c);
}
}
}
} catch (Exception ex) {
LOG.error("Problem loading properties for " + clazz.getName(), ex);
}
// Process annotations
Annotation[] annotations = clazz.getAnnotations();
for (Annotation annotation : annotations) {
if (annotation instanceof Conversion) {
Conversion conversion = (Conversion) annotation;
for (TypeConversion tc : conversion.conversions()) {
String key = tc.key();
if (mapping.containsKey(key)) {
break;
}
if (LOG.isDebugEnabled()) {
LOG.debug(key + ":" + key);
}
if (key != null) {
try {
if (tc.type() == ConversionType.APPLICATION) {
defaultMappings.put(key, createTypeConverter(tc.converter()));
} else {
if (tc.rule().toString().equals(ConversionRule.KEY_PROPERTY) || tc.rule().toString().equals(ConversionRule.CREATE_IF_NULL)) {
mapping.put(key, tc.value());
}
//for properties of classes
else if (!(tc.rule().toString().equals(ConversionRule.ELEMENT.toString())) ||
tc.rule().toString().equals(ConversionRule.KEY.toString()) ||
tc.rule().toString().equals(ConversionRule.COLLECTION.toString())
) {
mapping.put(key, createTypeConverter(tc.converter()));
}
//for keys of Maps
else if (tc.rule().toString().equals(ConversionRule.KEY.toString())) {
Class converterClass = Thread.currentThread().getContextClassLoader().loadClass(tc.converter());
if (LOG.isDebugEnabled()) {
LOG.debug("Converter class: " + converterClass);
}
//check if the converter is a type converter if it is one
//then just put it in the map as is. Otherwise
//put a value in for the type converter of the class
if (converterClass.isAssignableFrom(TypeConverter.class)) {
mapping.put(key, createTypeConverter(tc.converter()));
} else {
mapping.put(key, converterClass);
if (LOG.isDebugEnabled()) {
LOG.debug("Object placed in mapping for key "
+ key
+ " is "
+ mapping.get(key));
}
}
}
//elements(values) of maps / lists
else {
mapping.put(key, Thread.currentThread().getContextClassLoader().loadClass(tc.converter()));
}
}
} catch (Exception e) {
}
}
}
}
}
Method[] methods = clazz.getMethods();
for (Method method : methods) {
annotations = method.getAnnotations();
for (Annotation annotation : annotations) {
if (annotation instanceof TypeConversion) {
TypeConversion tc = (TypeConversion) annotation;
String key = tc.key();
if (mapping.containsKey(key)) {
break;
}
// Default to the property name
if ( key != null && key.length() == 0) {
key = AnnotationUtils.resolvePropertyName(method);
LOG.debug("key from method name... " + key + " - " + method.getName());
}
if (LOG.isDebugEnabled()) {
LOG.debug(key + ":" + key);
}
if (key != null) {
try {
if (tc.type() == ConversionType.APPLICATION) {
defaultMappings.put(key, createTypeConverter(tc.converter()));
} else {
if (tc.rule().toString().equals(ConversionRule.KEY_PROPERTY)) {
mapping.put(key, tc.value());
}
//for properties of classes
else if (!(tc.rule().toString().equals(ConversionRule.ELEMENT.toString())) ||
tc.rule().toString().equals(ConversionRule.KEY.toString()) ||
tc.rule().toString().equals(ConversionRule.COLLECTION.toString())
) {
mapping.put(key, createTypeConverter(tc.converter()));
}
//for keys of Maps
else if (tc.rule().toString().equals(ConversionRule.KEY.toString())) {
Class converterClass = Thread.currentThread().getContextClassLoader().loadClass(tc.converter());
if (LOG.isDebugEnabled()) {
LOG.debug("Converter class: " + converterClass);
}
//check if the converter is a type converter if it is one
//then just put it in the map as is. Otherwise
//put a value in for the type converter of the class
if (converterClass.isAssignableFrom(TypeConverter.class)) {
mapping.put(key, createTypeConverter(tc.converter()));
} else {
mapping.put(key, converterClass);
if (LOG.isDebugEnabled()) {
LOG.debug("Object placed in mapping for key "
+ key
+ " is "
+ mapping.get(key));
}
}
}
//elements(values) of maps / lists
else {
mapping.put(key, Thread.currentThread().getContextClassLoader().loadClass(tc.converter()));
}
}
} catch (Exception e) {
}
}
}
}
}
}
Looks for converter mappings for the specified class and adds it to an existing map. Only new converters are
added. If a converter is defined on a key that already exists, the converter is ignored. |
public static String buildConverterFilename(Class clazz) {
String className = clazz.getName();
String resource = className.replace('.", '/") + "-conversion.properties";
return resource;
}
|
public Object convertValue(Map map,
Object o,
Class aClass) {
return convertValue(map, null, null, null, o, aClass);
}
|
public Object convertValue(Map context,
Object target,
Member member,
String property,
Object value,
Class toClass) {
//
// Process the conversion using the default mappings, if one exists
//
TypeConverter tc = null;
if ((value != null) && (toClass == value.getClass())) {
return value;
}
// allow this method to be called without any context
// i.e. it can be called with as little as "Object value" and "Class toClass"
if (target != null) {
Class clazz = target.getClass();
Object[] classProp = null;
// this is to handle weird issues with setValue with a different type
if ((target instanceof CompoundRoot) && (context != null)) {
classProp = getClassProperty(context);
}
if (classProp != null) {
clazz = (Class) classProp[0];
property = (String) classProp[1];
}
tc = (TypeConverter) getConverter(clazz, property);
if (LOG.isDebugEnabled())
LOG.debug("field-level type converter for property ["+property+"] = "+(tc==null?"none found":tc));
}
if (tc == null && context != null) {
// ok, let's see if we can look it up by path as requested in XW-297
Object lastPropertyPath = context.get(ReflectionContextState.CURRENT_PROPERTY_PATH);
Class clazz = (Class) context.get(XWorkConverter.LAST_BEAN_CLASS_ACCESSED);
if (lastPropertyPath != null && clazz != null) {
String path = lastPropertyPath + "." + property;
tc = (TypeConverter) getConverter(clazz, path);
}
}
if (tc == null) {
if (toClass.equals(String.class) && (value != null) && !(value.getClass().equals(String.class) || value.getClass().equals(String[].class)))
{
// when converting to a string, use the source target's class's converter
tc = lookup(value.getClass());
} else {
// when converting from a string, use the toClass's converter
tc = lookup(toClass);
}
if (LOG.isDebugEnabled())
LOG.debug("global-level type converter for property ["+property+"] = "+(tc==null?"none found":tc));
}
if (tc != null) {
try {
return tc.convertValue(context, target, member, property, value, toClass);
} catch (Exception e) {
e.printStackTrace();
handleConversionException(context, property, value, target);
return TypeConverter.NO_CONVERSION_POSSIBLE;
}
}
if (defaultTypeConverter != null) {
try {
if (LOG.isDebugEnabled())
LOG.debug("falling back to default type converter ["+defaultTypeConverter+"]");
return defaultTypeConverter.convertValue(context, target, member, property, value, toClass);
} catch (Exception e) {
e.printStackTrace();
handleConversionException(context, property, value, target);
return TypeConverter.NO_CONVERSION_POSSIBLE;
}
} else {
try {
if (LOG.isDebugEnabled())
LOG.debug("falling back to Ognl's default type conversion");
return super.convertValue(value, toClass);
} catch (Exception e) {
e.printStackTrace();
handleConversionException(context, property, value, target);
return TypeConverter.NO_CONVERSION_POSSIBLE;
}
}
}
Convert value from one form to another.
Minimum requirement of arguments:
- supplying context, toClass and value
- supplying context, target and value.
|
TypeConverter createTypeConverter(String className) throws Exception {
// type converters are used across users
Object obj = objectFactory.buildBean(className, null);
if (obj instanceof TypeConverter) {
return (TypeConverter) obj;
// For backwards compatibility
} else if (obj instanceof ognl.TypeConverter) {
return new XWorkTypeConverterWrapper((ognl.TypeConverter)obj);
} else {
throw new IllegalArgumentException("Type converter class "+obj.getClass()+" doesn't implement com.opensymphony.xwork2.conversion.TypeConverter");
}
}
|
public static String getConversionErrorMessage(String propertyName,
ValueStack stack) {
String defaultMessage = LocalizedTextUtil.findDefaultText(XWorkMessages.DEFAULT_INVALID_FIELDVALUE,
ActionContext.getContext().getLocale(),
new Object[]{
propertyName
});
String getTextExpression = "getText('" + CONVERSION_ERROR_PROPERTY_PREFIX + propertyName + "','" + defaultMessage + "')";
String message = (String) stack.findValue(getTextExpression);
if (message == null) {
message = defaultMessage;
}
return message;
}
|
protected Object getConverter(Class clazz,
String property) {
if (LOG.isDebugEnabled()) {
LOG.debug("Property: " + property);
LOG.debug("Class: " + clazz.getName());
}
synchronized (clazz) {
if ((property != null) && !noMapping.contains(clazz)) {
try {
Map< String, Object > mapping = mappings.get(clazz);
if (mapping == null) {
mapping = buildConverterMapping(clazz);
} else {
mapping = conditionalReload(clazz, mapping);
}
Object converter = mapping.get(property);
if (LOG.isDebugEnabled() && converter == null) {
LOG.debug("converter is null for property " + property + ". Mapping size: " + mapping.size());
Iterator< String > iter = mapping.keySet().iterator();
while (iter.hasNext()) {
String next = iter.next();
LOG.debug(next + ":" + mapping.get(next));
}
}
return converter;
} catch (Throwable t) {
noMapping.add(clazz);
}
}
}
return null;
}
|
protected void handleConversionException(Map context,
String property,
Object value,
Object object) {
if ((Boolean.TRUE.equals(context.get(REPORT_CONVERSION_ERRORS)))) {
String realProperty = property;
String fullName = (String) context.get(CONVERSION_PROPERTY_FULLNAME);
if (fullName != null) {
realProperty = fullName;
}
Map conversionErrors = (Map) context.get(ActionContext.CONVERSION_ERRORS);
if (conversionErrors == null) {
conversionErrors = new HashMap();
context.put(ActionContext.CONVERSION_ERRORS, conversionErrors);
}
conversionErrors.put(realProperty, value);
}
}
|
public void loadConversionProperties(String propsName) throws IOException {
Iterator< URL > resources = ClassLoaderUtil.getResources(propsName, getClass(), true);
while (resources.hasNext()) {
URL url = resources.next();
Properties props = new Properties();
props.load(url.openStream());
if (LOG.isDebugEnabled()) {
LOG.debug("processing conversion file ["+propsName+"]");
}
for (Iterator iterator = props.entrySet().iterator(); iterator.hasNext();) {
Map.Entry entry = (Map.Entry) iterator.next();
String key = (String) entry.getKey();
try {
TypeConverter _typeConverter = createTypeConverter((String) entry.getValue());
if (LOG.isDebugEnabled()) {
LOG.debug("\t"+key + ":" + entry.getValue()+" [treated as TypeConverter "+_typeConverter+"]");
}
defaultMappings.put(key, _typeConverter);
} catch (Exception e) {
LOG.error("Conversion registration error", e);
}
}
}
}
|
public TypeConverter lookup(String className) {
if (unknownMappings.contains(className) && !defaultMappings.containsKey(className)) {
return null;
}
TypeConverter result = (TypeConverter) defaultMappings.get(className);
//Looks for super classes
if (result == null) {
Class clazz = null;
try {
clazz = Thread.currentThread().getContextClassLoader().loadClass(className);
} catch (ClassNotFoundException cnfe) {
}
result = lookupSuper(clazz);
if (result != null) {
//Register now, the next lookup will be faster
registerConverter(className, result);
} else {
// if it isn't found, never look again (also faster)
registerConverterNotFound(className);
}
}
return result;
}
Looks for a TypeConverter in the default mappings. |
public TypeConverter lookup(Class clazz) {
return lookup(clazz.getName());
}
Looks for a TypeConverter in the default mappings. |
TypeConverter lookupSuper(Class clazz) {
TypeConverter result = null;
if (clazz != null) {
result = (TypeConverter) defaultMappings.get(clazz.getName());
if (result == null) {
// Looks for direct interfaces (depth = 1 )
Class[] interfaces = clazz.getInterfaces();
for (int i = 0; i < interfaces.length; i++) {
if (defaultMappings.containsKey(interfaces[i].getName())) {
result = (TypeConverter) defaultMappings.get(interfaces[i].getName());
break;
}
}
if (result == null) {
// Looks for the superclass
// If 'clazz' is the Object class, an interface, a primitive type or void then clazz.getSuperClass() returns null
result = lookupSuper(clazz.getSuperclass());
}
}
}
return result;
}
Recurses through a class' interfaces and class hierarchy looking for a TypeConverter in the default mapping that
can handle the specified class. |
public synchronized void registerConverter(String className,
TypeConverter converter) {
defaultMappings.put(className, converter);
if ( unknownMappings.contains(className)) {
unknownMappings.remove(className);
}
}
|
public synchronized void registerConverterNotFound(String className) {
unknownMappings.add(className);
}
|
public void setDefaultTypeConverter(XWorkBasicConverter conv) {
this.defaultTypeConverter = conv;
}
|
public void setObjectFactory(ObjectFactory factory) {
this.objectFactory = factory;
try {
// note: this file is deprecated
loadConversionProperties("xwork-default-conversion.properties");
} catch (Exception e) {
}
try {
loadConversionProperties("xwork-conversion.properties");
} catch (Exception e) {
}
}
|