diff --git a/framework/base/src/main/java/org/apache/ofbiz/base/util/template/FreeMarkerWorker.java b/framework/base/src/main/java/org/apache/ofbiz/base/util/template/FreeMarkerWorker.java index 61fa33b0a63..09deeea2ee3 100644 --- a/framework/base/src/main/java/org/apache/ofbiz/base/util/template/FreeMarkerWorker.java +++ b/framework/base/src/main/java/org/apache/ofbiz/base/util/template/FreeMarkerWorker.java @@ -54,7 +54,6 @@ import freemarker.core.TemplateClassResolver; import freemarker.ext.beans.BeanModel; import freemarker.ext.beans.BeansWrapper; -import freemarker.ext.beans.BeansWrapperBuilder; import freemarker.template.Configuration; import freemarker.template.SimpleHash; import freemarker.template.SimpleScalar; @@ -80,7 +79,7 @@ private FreeMarkerWorker() { } // or maybe not for performance reasons... hmmm, leave to config file... private static final UtilCache CACHED_TEMPLATES = UtilCache.createUtilCache("template.ftl.general", 0, 0, false); - private static final BeansWrapper DEFAULT_OFBIZ_WRAPPER = new BeansWrapperBuilder(VERSION).build(); + private static final BeansWrapper DEFAULT_OFBIZ_WRAPPER = new OfbizBeansWrapper(VERSION); private static final TemplateHashModel DEFAULT_STATIC_MODELS = getConfiguredStaticModel(getDefaultOfbizWrapper()); private static final Configuration DEFAULT_OFBIZ_CONFIG = makeConfiguration(DEFAULT_OFBIZ_WRAPPER); diff --git a/framework/base/src/main/java/org/apache/ofbiz/base/util/template/OfbizBeansWrapper.java b/framework/base/src/main/java/org/apache/ofbiz/base/util/template/OfbizBeansWrapper.java new file mode 100644 index 00000000000..a440721e743 --- /dev/null +++ b/framework/base/src/main/java/org/apache/ofbiz/base/util/template/OfbizBeansWrapper.java @@ -0,0 +1,79 @@ +/******************************************************************************* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + *******************************************************************************/ +package org.apache.ofbiz.base.util.template; + +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +import freemarker.ext.beans.BeansWrapper; +import freemarker.ext.beans.MapModel; +import freemarker.template.TemplateModel; +import freemarker.template.TemplateModelException; +import freemarker.template.Version; + +/** + * The {@link BeansWrapper} that exposes Java objects to OFBiz FreeMarker templates. + * + *

It behaves like {@code BeansWrapper} in every respect but one: for {@link Map} values it + * enumerates only the map's own keys. FreeMarker's stock {@link MapModel} reports the union of the + * map's keys and the bean property names of the map object, so {@code ?keys}, {@code ?values}, + * {@code ?size} and {@code <#list aMap as key, value>} all see accessors such as {@code getClass} or + * {@code entrySet} mixed in with the real entries (OFBIZ-13164). + * + *

Only key enumeration changes. Member lookup still falls back to the bean model, so templates + * can keep calling methods on maps — including on {@code GenericValue}, which implements {@code Map}. + */ +public class OfbizBeansWrapper extends BeansWrapper { + + public OfbizBeansWrapper(Version version) { + super(version); + } + + @Override + public TemplateModel wrap(Object object) throws TemplateModelException { + // A TemplateModel is left to the superclass, which passes it through untouched even when it + // also happens to be a Map. + if (object instanceof Map && !(object instanceof TemplateModel)) { + return new MapEntryKeysModel((Map) object, this); + } + return super.wrap(object); + } + + /** + * A {@link MapModel} that enumerates the map's own keys rather than the union of those keys and + * the bean property names of the map object. + */ + private static final class MapEntryKeysModel extends MapModel { + private final Map map; + + MapEntryKeysModel(Map map, BeansWrapper wrapper) { + super(map, wrapper); + this.map = map; + } + + @Override + protected Set keySet() { + // A modifiable copy: MapModel's own implementation adds to the set it gets from BeanModel, + // while maps such as GenericEntity and MapContext return an unmodifiable key set. Copying + // also preserves the map's iteration order. + return new LinkedHashSet<>(map.keySet()); + } + } +} diff --git a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/HtmlWidget.java b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/HtmlWidget.java index a4965df51b6..a413311d382 100644 --- a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/HtmlWidget.java +++ b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/HtmlWidget.java @@ -42,6 +42,7 @@ import org.apache.ofbiz.base.util.collections.MapStack; import org.apache.ofbiz.base.util.string.FlexibleStringExpander; import org.apache.ofbiz.base.util.template.FreeMarkerWorker; +import org.apache.ofbiz.base.util.template.OfbizBeansWrapper; import org.apache.ofbiz.widget.renderer.ScreenRenderer; import org.apache.ofbiz.widget.renderer.ScreenStringRenderer; import org.apache.ofbiz.widget.renderer.html.HtmlWidgetRenderer; @@ -77,7 +78,7 @@ public class HtmlWidget extends ModelScreenWidget { SPECIAL_CONFIG_SQUARE_INTERPOLATION.setInterpolationSyntax(Configuration.SQUARE_BRACKET_INTERPOLATION_SYNTAX); } // not sure if this is the best way to get FTL to use my fancy MapModel derivative, but should work at least... - public static class ExtendedWrapper extends BeansWrapper { + public static class ExtendedWrapper extends OfbizBeansWrapper { public ExtendedWrapper(Version version) { super(version); }