I've in front of
this problem myself.
It seems that in a
future version of iBatis, this bug will be corrected by adding a "var" attribute
in iterate tag.
If you wan't a patch
to apply now, here's how I modified the code from iBatis.
(you most likely
downloaded the source code with the binary)
I can send a patch
to someone who ask (in a .jar file replacing ibatis-sqlmap-2.jar), but I would
prefer that someone makes a real modification to the framework. I have an idea
of how things should be changed to support nested iterate in a proper
way.
To use my patch, do
the following.
<iterate
property="list">
table.someFiled = #list[].value#
<iterate property="list[].list">
table.narrowField = #list[].list[].value#
</iterate>
</iterate>
Please note that
this modification was made on the iBatis 2.1.7.
A bug still remains
in my modifcations. I can make a new patch if anyone asks. This bug will make
iBatis crash in this case.
<iterate
property="list">
<iterate property="list[].list">
<!-- suppose here you always want the value of element 4
in first list>
table.field = #list[4].list[].value#
</iterate>
</iterate>
// Mofications made
to 3 files. Modifcations begin with comment "// Added code" and ends with
"// End of added code". Some code is replaced to surrunded by "// Replaced code"
and "// End of replaced code"
// classes modified
: com.ibatis.sqlmap.engine.mapping.sql.dynamic.DynamicSQL,
com.ibatis.sqlmap.engine.mapping.sql.dynamic.elements.IterateTagHandler,
com.ibatis.sqlmap.engine.mapping.sql.dynamic.elements.ConditionnalTagHandler
In
com.ibatis.sqlmap.engine.mapping.sql.dynamic.DynamicSQL
protected
void iteratePropertyReplace(StringBuffer bodyContent, IterateContext iterate)
{
if(iterate!=null) {
String find = iterate.getProperty() + "[]";
// Added code.
find = find.replaceAll("\\[\\d+\\]", "[]");
// End of added code.
String replace = iterate.getProperty() + "[" + iterate.getIndex() + "]";
replace(bodyContent, find, replace);
}
}
private void processBodyChildren(RequestScope request, SqlTagContext ctx, Object parameterObject, Iterator localChildren, PrintWriter out) {
while (localChildren.hasNext()) {
SqlChild child = (SqlChild) localChildren.next();
if (child instanceof SqlText) {
SqlText sqlText = (SqlText) child;
String sqlStatement = sqlText.getText();
if (sqlText.isWhiteSpace()) {
out.print(sqlStatement);
} else if (!sqlText.isPostParseRequired()) {
if(iterate!=null) {
String find = iterate.getProperty() + "[]";
// Added code.
find = find.replaceAll("\\[\\d+\\]", "[]");
// End of added code.
String replace = iterate.getProperty() + "[" + iterate.getIndex() + "]";
replace(bodyContent, find, replace);
}
}
private void processBodyChildren(RequestScope request, SqlTagContext ctx, Object parameterObject, Iterator localChildren, PrintWriter out) {
while (localChildren.hasNext()) {
SqlChild child = (SqlChild) localChildren.next();
if (child instanceof SqlText) {
SqlText sqlText = (SqlText) child;
String sqlStatement = sqlText.getText();
if (sqlText.isWhiteSpace()) {
out.print(sqlStatement);
} else if (!sqlText.isPostParseRequired()) {
// BODY OUT
out.print(sqlStatement);
out.print(sqlStatement);
ParameterMapping[] mappings =
sqlText.getParameterMappings();
if (mappings != null) {
for (int i = 0, n = mappings.length; i < n; i++) {
ctx.addParameterMapping(mappings[i]);
}
}
} else {
if (mappings != null) {
for (int i = 0, n = mappings.length; i < n; i++) {
ctx.addParameterMapping(mappings[i]);
}
}
} else {
IterateContext itCtx = ctx.peekIterateContext();
if(null != itCtx &&
itCtx.isAllowNext()){
itCtx.next();
itCtx.setAllowNext(false);
if(!itCtx.hasNext()) {
itCtx.setFinal(true);
}
}
itCtx.next();
itCtx.setAllowNext(false);
if(!itCtx.hasNext()) {
itCtx.setFinal(true);
}
}
if(itCtx!=null)
{
StringBuffer sqlStatementBuffer = new StringBuffer(sqlStatement);
iteratePropertyReplace(sqlStatementBuffer, itCtx);
sqlStatement = sqlStatementBuffer.toString();
}
StringBuffer sqlStatementBuffer = new StringBuffer(sqlStatement);
iteratePropertyReplace(sqlStatementBuffer, itCtx);
sqlStatement = sqlStatementBuffer.toString();
}
sqlText = PARAM_PARSER.parseInlineParameterMap(delegate.getTypeHandlerFactory(),
sqlStatement);
ParameterMapping[] mappings =
sqlText.getParameterMappings();
out.print(sqlText.getText());
if (mappings != null) {
for (int i = 0, n = mappings.length; i < n; i++) {
ctx.addParameterMapping(mappings[i]);
}
}
}
} else if (child instanceof SqlTag) {
SqlTag tag = (SqlTag) child;
SqlTagHandler handler = tag.getHandler();
int response = SqlTagHandler.INCLUDE_BODY;
do {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
response = handler.doStartFragment(ctx, tag, parameterObject);
if (response != SqlTagHandler.SKIP_BODY) {
out.print(sqlText.getText());
if (mappings != null) {
for (int i = 0, n = mappings.length; i < n; i++) {
ctx.addParameterMapping(mappings[i]);
}
}
}
} else if (child instanceof SqlTag) {
SqlTag tag = (SqlTag) child;
SqlTagHandler handler = tag.getHandler();
int response = SqlTagHandler.INCLUDE_BODY;
do {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
response = handler.doStartFragment(ctx, tag, parameterObject);
if (response != SqlTagHandler.SKIP_BODY) {
processBodyChildren(request, ctx, parameterObject, tag.getChildren(),
pw);
pw.flush();
pw.close();
StringBuffer body = sw.getBuffer();
response = handler.doEndFragment(ctx, tag, parameterObject, body);
handler.doPrepend(ctx, tag, parameterObject, body);
if (response != SqlTagHandler.SKIP_BODY) {
if (body.length() > 0) {
out.print(body.toString());
}
}
pw.flush();
pw.close();
StringBuffer body = sw.getBuffer();
response = handler.doEndFragment(ctx, tag, parameterObject, body);
handler.doPrepend(ctx, tag, parameterObject, body);
if (response != SqlTagHandler.SKIP_BODY) {
if (body.length() > 0) {
out.print(body.toString());
}
}
}
} while (response == SqlTagHandler.REPEAT_BODY);
} while (response == SqlTagHandler.REPEAT_BODY);
ctx.popRemoveFirstPrependMarker(tag);
if(ctx.peekIterateContext()!= null && ctx.peekIterateContext().getTag()
== tag) {
// Added code.
ctx.setAttribute(ctx.peekIterateContext().getTag(), null);
// End of added code.
ctx.popIterateContext();
}
// Added code.
ctx.setAttribute(ctx.peekIterateContext().getTag(), null);
// End of added code.
ctx.popIterateContext();
}
}
}
}
}
}
In com.ibatis.sqlmap.engine.mapping.sql.dynamic.elements.IterateTagHandler
public int doStartFragment(SqlTagContext
ctx, SqlTag tag, Object parameterObject) {
IterateContext iterate = (IterateContext) ctx.getAttribute(tag);
if (iterate == null) {
ctx.pushRemoveFirstPrependMarker(tag);
Object collection;
String prop = tag.getPropertyAttr();
if (prop != null) {
// Added code.
{
// Increase index if the first tag is another iterate.
IterateContext itCtx = ctx.peekIterateContext();
if(null != itCtx && itCtx.isAllowNext()){
itCtx.next();
itCtx.setAllowNext(false);
if(!itCtx.hasNext()) {
itCtx.setFinal(true);
}
}
}
SqlTag parentTag = tag.getParent();
int listIndex = prop.lastIndexOf('[');
while (listIndex > -1) {
if (Character.isDigit(prop.charAt(listIndex + 1))) {
// Skip to next list.
if (listIndex > 0) {
listIndex = prop.lastIndexOf('[', listIndex - 1);
}
else {
listIndex = -1;
}
}
else {
// Add iteration number to property.
Object parentContext = ctx.getAttribute(parentTag);
while (!(parentContext instanceof IterateContext)) {
parentTag = parentTag.getParent();
parentContext = ctx.getAttribute(parentTag);
}
IterateContext parentIterateContext = (IterateContext) parentContext;
prop = prop.substring(0, listIndex + 1) + parentIterateContext.getIndex() + prop.substring(listIndex + 1);
// Skip to next list.
if (listIndex > 0) {
listIndex = prop.lastIndexOf('[', listIndex - 1);
}
else {
listIndex = -1;
}
parentTag = parentTag.getParent();
}
}
// End of added code.
collection = PROBE.getObject(parameterObject, prop);
} else {
collection = parameterObject;
}
iterate = new IterateContext(collection,tag);
iterate.setProperty( null == prop ? "" : prop );
ctx.setAttribute(tag, iterate);
ctx.pushIterateContext(iterate);
}
if (iterate != null && iterate.hasNext()) {
return INCLUDE_BODY;
} else {
return SKIP_BODY;
}
}
IterateContext iterate = (IterateContext) ctx.getAttribute(tag);
if (iterate == null) {
ctx.pushRemoveFirstPrependMarker(tag);
Object collection;
String prop = tag.getPropertyAttr();
if (prop != null) {
// Added code.
{
// Increase index if the first tag is another iterate.
IterateContext itCtx = ctx.peekIterateContext();
if(null != itCtx && itCtx.isAllowNext()){
itCtx.next();
itCtx.setAllowNext(false);
if(!itCtx.hasNext()) {
itCtx.setFinal(true);
}
}
}
SqlTag parentTag = tag.getParent();
int listIndex = prop.lastIndexOf('[');
while (listIndex > -1) {
if (Character.isDigit(prop.charAt(listIndex + 1))) {
// Skip to next list.
if (listIndex > 0) {
listIndex = prop.lastIndexOf('[', listIndex - 1);
}
else {
listIndex = -1;
}
}
else {
// Add iteration number to property.
Object parentContext = ctx.getAttribute(parentTag);
while (!(parentContext instanceof IterateContext)) {
parentTag = parentTag.getParent();
parentContext = ctx.getAttribute(parentTag);
}
IterateContext parentIterateContext = (IterateContext) parentContext;
prop = prop.substring(0, listIndex + 1) + parentIterateContext.getIndex() + prop.substring(listIndex + 1);
// Skip to next list.
if (listIndex > 0) {
listIndex = prop.lastIndexOf('[', listIndex - 1);
}
else {
listIndex = -1;
}
parentTag = parentTag.getParent();
}
}
// End of added code.
collection = PROBE.getObject(parameterObject, prop);
} else {
collection = parameterObject;
}
iterate = new IterateContext(collection,tag);
iterate.setProperty( null == prop ? "" : prop );
ctx.setAttribute(tag, iterate);
ctx.pushIterateContext(iterate);
}
if (iterate != null && iterate.hasNext()) {
return INCLUDE_BODY;
} else {
return SKIP_BODY;
}
}
In com.ibatis.sqlmap.engine.mapping.sql.dynamic.elements.ConditionnalTagHandler
protected long compare(SqlTagContext ctx, SqlTag tag, Object parameterObject) {
String propertyName = tag.getPropertyAttr();
String comparePropertyName = tag.getComparePropertyAttr();
String compareValue = tag.getCompareValueAttr();
String prop =
tag.getPropertyAttr();
Object value1;
Class type;
IterateContext itCtx = ctx.peekIterateContext();
Object value1;
Class type;
IterateContext itCtx = ctx.peekIterateContext();
if (prop != null) {
if(null != itCtx
&& itCtx.isAllowNext()){
itCtx.next();
itCtx.setAllowNext(false);
if(!itCtx.hasNext()) {
itCtx.setFinal(true);
}
}
itCtx.next();
itCtx.setAllowNext(false);
if(!itCtx.hasNext()) {
itCtx.setFinal(true);
}
}
if(prop.indexOf(START_INDEX) > -1)
{
// Replaced code.
propertyName = new StringBuffer(propertyName).insert(
propertyName.lastIndexOf(END_INDEX),itCtx.getIndex()).toString();
// End of replaced code.
// Added code.
SqlTag parentTag = itCtx.getTag().getParent();
int listIndex = propertyName.lastIndexOf('[');
while (listIndex > -1) {
if (Character.isDigit(propertyName.charAt(listIndex + 1))) {
// Skip to next list.
if (listIndex > 0) {
listIndex = propertyName.lastIndexOf('[', listIndex - 1);
}
else {
listIndex = -1;
}
}
else {
// Add iteration number to property.
Object parentContext = ctx.getAttribute(parentTag);
while (!(parentContext instanceof IterateContext)) {
parentTag = parentTag.getParent();
parentContext = ctx.getAttribute(parentTag);
}
IterateContext parentIterateContext = (IterateContext) parentContext;
propertyName = propertyName.substring(0, listIndex + 1) + parentIterateContext.getIndex() + propertyName.substring(listIndex + 1);
// Skip to next list.
if (listIndex > 0) {
listIndex = propertyName.lastIndexOf('[', listIndex - 1);
}
else {
listIndex = -1;
}
parentTag = parentTag.getParent();
}
}
// End of added code.
}
// Replaced code.
propertyName = new StringBuffer(propertyName).insert(
propertyName.lastIndexOf(END_INDEX),itCtx.getIndex()).toString();
// End of replaced code.
// Added code.
SqlTag parentTag = itCtx.getTag().getParent();
int listIndex = propertyName.lastIndexOf('[');
while (listIndex > -1) {
if (Character.isDigit(propertyName.charAt(listIndex + 1))) {
// Skip to next list.
if (listIndex > 0) {
listIndex = propertyName.lastIndexOf('[', listIndex - 1);
}
else {
listIndex = -1;
}
}
else {
// Add iteration number to property.
Object parentContext = ctx.getAttribute(parentTag);
while (!(parentContext instanceof IterateContext)) {
parentTag = parentTag.getParent();
parentContext = ctx.getAttribute(parentTag);
}
IterateContext parentIterateContext = (IterateContext) parentContext;
propertyName = propertyName.substring(0, listIndex + 1) + parentIterateContext.getIndex() + propertyName.substring(listIndex + 1);
// Skip to next list.
if (listIndex > 0) {
listIndex = propertyName.lastIndexOf('[', listIndex - 1);
}
else {
listIndex = -1;
}
parentTag = parentTag.getParent();
}
}
// End of added code.
}
value1 =
PROBE.getObject(parameterObject,
propertyName);
type = PROBE.getPropertyTypeForGetter(parameterObject, propertyName);
} else {
value1 = parameterObject;
if (value1 != null) {
type = parameterObject.getClass();
} else {
type = Object.class;
}
}
if (comparePropertyName != null) {
Object value2 = PROBE.getObject(parameterObject, comparePropertyName);
return compareValues(type, value1, value2);
} else if (compareValue != null) {
return compareValues(type, value1, compareValue);
} else {
throw new NestedRuntimeException("Error comparing in conditional fragment. Uknown 'compare to' values.");
}
}
type = PROBE.getPropertyTypeForGetter(parameterObject, propertyName);
} else {
value1 = parameterObject;
if (value1 != null) {
type = parameterObject.getClass();
} else {
type = Object.class;
}
}
if (comparePropertyName != null) {
Object value2 = PROBE.getObject(parameterObject, comparePropertyName);
return compareValues(type, value1, value2);
} else if (compareValue != null) {
return compareValues(type, value1, compareValue);
} else {
throw new NestedRuntimeException("Error comparing in conditional fragment. Uknown 'compare to' values.");
}
}
