PDA

View Full Version : Unexpected type returned from getValue() in custom property editor


dgschwind
May 22nd, 2007, 08:53 PM
Hello,

We are using Spring MVC as part of the Spring 2.0.4 release, and are binding custom property editors for use in handling end user edits to springform:select tag rendered drop down boxes. The oddity that we are seeing does not appear to be fixed in the 2.0.5 release due to the changelog entries present with the 2.0.5 release. Thus, I want to pose this question to the forum for whatever result that may yield.

Here is the problem. We have a Language class that has a textual description and a unique system generated identifier (i.e. surrogate key). What we are trying to accomplish is to have the following type of HTML emitted by the springform select tag:

<select id=... name=... size="1">
<option value="0">English</option>
<option value="1">French</option>
<option value="2">Spanish</option>
</select>

We are able to instruct the springform select tag to emit that form of HTML with the following JSP fragment:

<springform:select path="language" items="${languages}" itemValue="oid" />

Since we need to be able to represent a Language instance as a String and convert a String to a Language instance, we have a custom property editor registered to handle these conversions. However, what we are seeing is that the type returned from getValue() in the custom property editor varies, which then requires instanceof checking in the getAsText() implementation. Sometimes the type returned from getValue() is of type java.lang.Long (which indicates that the Language's getOid() method was used to extract the surrogate key and passed as an argument to the custom property editor's setValue() method) and sometimes the type returned is of our Language type.

This appears to be a defect in Spring MVC 2.0.4, but I was wondering if anyone visiting this list sees an error in our use of the spring form select tag, or believes this behavior is in fact a defect in the 2.0.4 implementation.

Regards,

Doug

cwilkes
May 22nd, 2007, 10:04 PM
This appears to be a defect in Spring MVC 2.0.4,

Can you post your code for your custom property editor? Also how and where are you registering it?

dgschwind
May 23rd, 2007, 10:57 AM
Sure. Here is the getAsText() method on our LanguagePropertyEditor, which shows the implementation dealing with the different types returned from getValue(). Ideally, we would expect getValue() to always return one type, and in this case Language.

@Override
public String getAsText()
{
String result = null;

if (getValue() instanceof Long)
{
result = Long.toString((Long) getValue());
}
else if (getValue() instanceof Language)
{
try
{
result = localizationContext.getLabelTextFor(((Language) getValue()).getLocalizedTextId());
}
catch (CouldNotReadException e)
{
//TODO: throw some kind of application error
e.printStackTrace();
}
}
return result;
}

An instance of our LanguagePropertyEditor gets bound into a ServletRequestDataBinder instance (binder) via the following line in our Controller's initBinder() method:

binder.registerCustomEditor(Language.class, new LanguagePropertyEditor(getLocalizationContext(requ est)));

Regards,

Doug

Jörg Heinicke
May 23rd, 2007, 12:00 PM
No implementation of getValue(), setValue() and setAsText()? Can you find out what calls setValue() with Long as param.

How does your object look like this Language instance is bound to? It has a field language?

Might it already work if you remove itemValue="oid" from the springform:select since that's what your property editor actually does. Anyway this property editor should not be called for oid since it's of type Long and the property editor is only (really?) registered for type Language.

Jörg

dgschwind
May 23rd, 2007, 04:26 PM
Thanks for the response Jorg. Here are answers to your questions ...

The LanguagePropertyEditor extends PropertyEditorSupport, and thus no, there are no overidden implementations of getValue() or setValue() provided in LanguagePropertyEditor. In terms of setAsText(), yes, there is an implementation provided for that method in LanguagePropertyEditor, but is only called on an HTTP POST (default method is post on the form for the given page) and thus only when spring binding is attempting to bind values into the underlying command bean. For completeness, I am including that method implementation here:

@Override
public void setAsText(String httpPostParameterValue)
{
LocalizationService constantService = ServiceFactory.getLocalizationService();

try
{
setValue(constantService.findLanguageById(Long.val ueOf(httpPostParameterValue)));
}
catch (CouldNotReadException e)
{
//TODO: throw some kind of application error
e.printStackTrace();
}
}

Yes, what appears to be calling setValue() with a Long instance as a parameter is the oid aspect of a Language instance, which according to my testing only occurs when the page is being rendered (as opposed to when end user edits are being applied to a command bean when the form is submitted)

In terms of your domain model related question, we have a UserPreferences instance which is the command bean/form backing object that is being edited. It has a Language property bound into it and UserPreferences.class exposes getLanguage()/setLanguage(). Each Language instance has an oid of type Long that represents the Language's primary (surrogate) key.

The select does *partially* work if itemValue="oid" is removed from the springform select tag attributes. What happens is the HTML produced looks like:

<select id=... name=...>
<option value="abc">abc</option
<option value="def">def</option
...
</select>

as opposed to what is desired (depicted below):

<select id=... name=...>
<option value="0">abc</option
<option value="1">def</option
...
</select>

We would like the HTML option values to be the primary key of the Language of interest, as opposed to its human readable representation. So, if we remove itemValue="oid", we then see that the LanguagePropertyEditor only sees instances of type Language returned from getValue().

Does that clarify the problem that we are seeing, and the motivation for persuing a solution?

Thanks,

Doug

cwilkes
May 23rd, 2007, 05:02 PM
Could you post your complete code inbetween code blocks? Makes it much easier to read.


if (getValue() instanceof Long){
result = Long.toString((Long) getValue());
}else if (getValue() instanceof Language){
try{
result = localizationContext.getLabelTextFor(((Language)get Value()).getLocalizedTextId());
}
catch (CouldNotReadException e)


I'm puzzled at what you're trying to do here. Either store a Long for your Value or a Language object. Don't try and mix and match. I would store the Language one and then in your getAsText have it do something like this:


if (getValue() == null) return null;
return ((Language)getValue()).getLanguageName()

cwilkes
May 23rd, 2007, 05:06 PM
<select id=... name=...>
<option value="0">abc</option
<option value="1">def</option
...
</select>

What you want to do is something like this:

class User {
Language language; // with getters and setters
}
class Lanuage {
Long id;
String name; // human readable name
}

Current language: ${command.language.name}
<select id="language">
<c:forEach var="lang" items="${alllanguages}">
<option value="${lang.id}">${lang.name}</option>
</c:forEach>
</select>

Jörg Heinicke
May 24th, 2007, 03:54 AM
Hi Doug,

your getAsText() seems to be wrong then. Since you want the Oid of Language as value, you should return that one in getAsText() and not the human readable form. The idea is that you need to convert exactly this value back to the Language instance in setAsText() (its implementation looks correct). I wonder if that's possible from the human readable form.

Jörg

dgschwind
May 24th, 2007, 11:34 AM
Hi Cwilkes and Jorg,

Thank you for those responses, my apologies for the difficult to read code blocks.

I fully agree on your common point of type homogeneity. getAsText() should find that getValue() returns type Language and that setAsText() results in a call to setValue() with an instance of Language. That is exactly what we want to happen. However, since we want the option value to be different than the displayed value (e.g. <option value="0">English</option>), we went down the path of including the itemValue="oid" attribute in the springform:select definition.

We may need to do exactly as you describe and use the springform:options tag or possibly iterate the languages present as cwilkes described to result in the HTML block we are looking for. That is a bit more verbose in the JSP than is ideal, but we may be forced to go that route.

Thanks again for your responses.

Regards,

Doug