Fixed: HashMaps are not properly rendered in FTL with current FTL integration (OFBIZ-13164) - #1615
Merged
mridulpathak merged 1 commit intoAug 10, 2026
Conversation
…egration (OFBIZ-13164)
FreeMarkerWorker built a plain BeansWrapper, so every java.util.Map reached
templates as freemarker.ext.beans.MapModel. MapModel.keySet() returns the
union of the map's own keys and the bean property names of the map object, so
key enumeration saw accessors mixed in with the real entries. On a two entry
map, ?size reported 30, ?keys listed getClass, put, entrySet and the rest
alongside alpha and beta, and <#list aMap as key, value> failed outright,
because the values behind those synthetic keys are methods rather than
strings.
Added OfbizBeansWrapper, which substitutes a MapModel whose key set is the
map's own. Only enumeration changes: member lookup still falls back to the
bean model, so templates keep calling methods on maps. That distinction
matters because GenericEntity implements Map, so switching to
DefaultObjectWrapper or enabling simpleMapWrapper would have broken every
${anEntity.getRelatedOne(...)}, ${anEntity.getString(...)} and
${aMap.get(...)} currently in the templates.
HtmlWidget.ExtendedWrapper now extends OfbizBeansWrapper so that screen
rendering picks up the same behaviour.
Two secondary effects are worth noting: ?keys now follows the map's iteration
order rather than being arbitrary, and an empty map now reports ?size 0,
which is consistent with the ?has_content it already reported.
Thanks to Carsten Schinzer for reporting this issue and for identifying the
BeansWrapper versus DefaultObjectWrapper mechanism behind it.
mridulpathak
added a commit
that referenced
this pull request
Aug 10, 2026
…egration (OFBIZ-13164) (#1616) Backported from trunk (#1615). OfbizBeansWrapper substitutes a MapModel whose key set is the map's own, instead of FreeMarker's stock union of the map's keys and its bean property names, so ?keys, ?size, and <#list map as key, value> stop seeing accessors like getClass or entrySet mixed in with the real entries; HtmlWidget.ExtendedWrapper picks up the same fix for screen rendering. Cherry-picked cleanly onto release24.09 with no conflicts and no adaptation needed. Thanks: Aditi Patel (author of the trunk fix) and Carsten Schinzer (original reporter). Co-authored-by: toaditi <aditi.patel@hotwax.co>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Jira: https://issues.apache.org/jira/browse/OFBIZ-13164
Thanks to Carsten Schinzer for reporting this one, and for naming the
BeansWrapperversusDefaultObjectWrapperdistinction in the report. Thatturned out to be exactly the right thread to pull, and the note that a simple
fix did not work saved me from taking the wrong turn.
What happens today
FreeMarkerWorkerbuilds a plainBeansWrapper, so everyjava.util.Mapreaches templates as
freemarker.ext.beans.MapModel.MapModel.keySet()returns the union of the map's own keys and the bean property names of the map
object. On a two entry
LinkedHashMapholding onlyalphaandbeta:${aMap?size}30${aMap?keys?join(",")}getClass,getOrDefault,values,computeIfAbsent,replace,...,alpha,class,keySet,beta,entrySet,...<#list aMap as key, value>Why I did not take either of the usual fixes
GenericEntityimplementsMap, so everyGenericValuein a template is a mapas far as the wrapper is concerned. That makes both of the obvious options quite
wide:
DefaultObjectWrapperalso swapsListforDefaultListAdapterand stopsmethod calls generally, giving
NonHashExceptionon${aList.size()}and${aBean.getSomething()}.BeansWrapperBuilder.setSimpleMapWrapper(true)is narrower, but still ends${aMap.get(...)}and${aMap.entrySet()}, along with every${anEntity.getRelatedOne(...)}and${anEntity.getString(...)}.Framework templates alone use
.get(in 161 files,keySet()in 14 and.size()in 40, before counting plugins, so I was wary of changing the wrapperwholesale.
The change
OfbizBeansWrappersubstitutes aMapModelwhose key set is the map's own, andoverrides nothing else.
get()is untouched, so member lookup still falls backto the bean model and templates keep calling methods on maps, including on
GenericValue.HtmlWidget.ExtendedWrappernow extends it so that screenrendering picks up the same behaviour.
Three files, 82 insertions, 3 deletions.
Two secondary effects
?keysnow follows the map's iteration order rather than being arbitrary.?sizeof0. On trunk it reports28while?has_contentalready reportsfalse, so this brings the two into agreement.Verification
0 skipped.
checkstyleMain,codenarcMain,codenarcTestandjavadocall pass, andthe new class compiles clean under
javac -Xlint:all.version the build pins, before and after the change, including the
GenericValueshaped case of a map that also exposes business methods.These checks are all unit level; I have not yet exercised the change in a
running instance, so a second pair of eyes on the screen rendering path would be
very welcome.
I kept this patch to the production change alone. If reviewers would prefer
regression tests alongside it, I have them ready and would be glad to push them
to this branch. They cover the three built ins above, plus guards that method
access still works both on plain maps and on map backed entity values.