5

I'm sending this parameter to my struts action

cdata[1]=bar

In my action I'm interested in the index and the value. I defined a getter/setter pair for CDATA as the OGNL documentation suggests:

public void setCdata(int index, String value){
    LOG.info("setData; key="+ key +"; value="+ value);
    // store index and value;
}

public String getCdata(int index){
    return null; // don't really need a setter
}

This is the Exception I get:

2013-04-29 15:38:49,792 [http-apr-8080-exec-3] WARN  com.opensymphony.xwork2.util.logging.commons.CommonsLogger.warn(CommonsLogger.java:60) - Error setting expression 'cdata[1]' with value '[Ljava.
lang.String;@4223d2a4'
ognl.OgnlException: target is null for setProperty(null, "1", [Ljava.lang.String;@4223d2a4)
        at ognl.OgnlRuntime.setProperty(OgnlRuntime.java:2309)
        at ognl.ASTProperty.setValueBody(ASTProperty.java:127)
        at ognl.SimpleNode.evaluateSetValueBody(SimpleNode.java:220)
        at ognl.SimpleNode.setValue(SimpleNode.java:301)
        at ognl.ASTChain.setValueBody(ASTChain.java:227)
        at ognl.SimpleNode.evaluateSetValueBody(SimpleNode.java:220)
        at ognl.SimpleNode.setValue(SimpleNode.java:301)
        at ognl.Ognl.setValue(Ognl.java:737)
        ...

If I define a public member variable String[] cdata = new String[1000] I don't see any exception in my log but my setter is not called either. If the member variable is private I get another exception again.

Roman C
  • 49,761
  • 33
  • 66
  • 176
kosmičák
  • 1,043
  • 1
  • 17
  • 41

3 Answers3

2

Use the following setup

List<String> cdata = new ArrayList<String>();

public List<String> getCdata() {
   return cdata;
}

public void setCdata(final List<String> cdata) {
    if (cdata == null) {
        this.cdata = new ArrayList<String>();
    } else {
        this.cdata = cdata;
    }
}

submit the values from JSP like cdata[1]=value etc

only requirement is to have the getters/setters. I've tested this Tomcat7 running on java 1.6. You can submit values like cdata[0], cdata[1] likewise

or else you could use a map

private Map<String, String> data = new HashMap<String, String>();

public Map<String, String> getData() {
    return data;
}

public void setData(Map<String, String> data) {
    this.data = data;
}

JSP can have

<s:form action="indexProperty">

    <h3>Test The Map</h3>
    <input type="text" name="data['0']"/>
    <input type="text" name="data['1']"/>

    <s:iterator value="data.entrySet()" var="aData">
        <s:property value="#aData.key" />-<s:property value="#aData.value" />
    </s:iterator>

    <input type="submit" name="submit" value="submit"/>
</s:form>

Gets populated without a issue

Dev Blanked
  • 8,555
  • 3
  • 26
  • 32
  • I copied all of your code added log output at the beginning of each. getCdataValues is the only method called followed by an Exception - I've just added the stack trace to my original question. – kosmičák Apr 30 '13 at 13:09
  • @kosmičák i've edited my answer. only requirement is to have proper getter/setters and use a list – Dev Blanked Apr 30 '13 at 17:25
  • Yes, this works... I use the same approach elsewhere in my application where the passed param. is a "real" array not sparse array as cdata here. The drawback is that if the index is 1000 and that's the only field passed it allocates space for all 1000 list items anyway. I was hoping ognl/struts gives me the possibility to take care of backing/mapping this sparce array myself and the OGNL documentation clearly states it should be possible: http://commons.apache.org/proper/commons-ognl/language-guide.html#Indexing I wonder how stuts uses ognl because the behavior is opposite. – kosmičák Apr 30 '13 at 22:01
  • @kosmičák may be you can use a special custom implementation of the list for this purpose... keep track of the size and indexes of not null elements and treat other indexes as null may be.... just a thought – Dev Blanked May 01 '13 at 05:12
  • I tried to use Map cdata and pass cdata['1']... Struts looks for cdata.set1(...) method. – kosmičák May 02 '13 at 07:01
  • my version is 2.3.8. I'm going to try your code later today and if it works will mark you answer as accepted. – kosmičák May 02 '13 at 12:25
  • Your code works and the solution is cleaner than my parsing the HttpServletRequest. When I used the Map backed cdata my setter's signature was setCdata(String index, String value) similarly to the setPropertyName(IndexType index, PropertyType value) example from the "OGNL Object Indexed Properties" section in OGNL docs: http://commons.apache.org/proper/commons-ognl/language-guide.html#Indexing – kosmičák May 02 '13 at 14:25
1

My solution (rather an ugly hack):

I made my action class implement ServletRequestAware and in the action iterate over the parameter map from HttpServletRequest, fetch cdata from it and parse it for index and value

I had to change the sent parameter and encode eg cdata[999]=foobar like cdata_999_=foobar because if it looks like an array field struts requires there's a setter/getter for it in the action class.

kosmičák
  • 1,043
  • 1
  • 17
  • 41
0

According to the docs, OGNL provides support for indexing properties of JavaBeans: OGNL Reference Guide:

JavaBeans supports the concept of Indexed properties. Specifically this means that an object has a set of methods that follow the following pattern:

public PropertyType[] getPropertyName();
public void setPropertyName(PropertyType[] anArray);
public PropertyType getPropertyName(int index);
public void setPropertyName(int index, PropertyType value);

You didn't implement all of these methods. Also if you didn't initialize an array, the values could not be set.

You can read more about indexed properties here.

Indexed Properties

An indexed property is an array instead of a single value. In this case, the bean class provides a method for getting and setting the entire array. Here is an example for an int[] property called testGrades:

public int[] getTestGrades() {
    return mTestGrades;
}

public void setTestGrades(int[] tg) {
    mTestGrades = tg;
}

For indexed properties, the bean class also provides methods for getting and setting a specific element of the array.

public int getTestGrades(int index) {
    return mTestGrades[index];
}

public void setTestGrades(int index, int grade) {
    mTestGrades[index] = grade;
}
Roman C
  • 49,761
  • 33
  • 66
  • 176
  • Yes, that's what the documentation says but I still don't get it. What's the second method supposed to do and if it provides the array that will be set by ognl what's the point of the 4th method then? – kosmičák Apr 30 '13 at 09:47
  • This means that you should create `String[] getCdata()` because you are using array. – Roman C Apr 30 '13 at 13:20
  • It didn't work with all four methods either. It didn't know how to initialize the String[]. I guess I'd have to initialize the array and know the highest possible index in advance otherwise I'd get an ArrayIndexOutOfBoundsException. The index might be 1000 but I don't need the space for the 999 previous array fields. It's a waste of space. And it still doesn't explain the forth method - if it gets the array, it can set the value at the specific index itself, why call my own setter to do it? – kosmičák Apr 30 '13 at 13:45
  • Not in your case the setter *will not* be called. To populate the value you need the space available for the caller. – Roman C Apr 30 '13 at 14:14
  • If you need more explanation look at [this](http://stackoverflow.com/questions/15650800/repopulate-an-arraylist-from-jsp-with-struts2/15657572#15657572) answer. It should bring light to your questions. – Roman C Apr 30 '13 at 14:28
  • I read the question including all answers and it doesn't seem to explain it. What are the 3rd and 4th method for then... who calls them? From the OGNL docs: http://commons.apache.org/proper/commons-ognl/language-guide.html#Indexing "References such as someProperty[2] are routed through the property accessor (setSomeProperty(2, value)). If there is no indexed property accessor a property is found with the name someProperty and the index is applied to that." That's exactly opposite of what happens-struts demands the property exists and ignores the accessor setSomeProperty(2, value). – kosmičák Apr 30 '13 at 15:48