This is an automated email from the ASF dual-hosted git repository.

mridulpathak pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/ofbiz-framework.git


The following commit(s) were added to refs/heads/trunk by this push:
     new 4ccdb3e917 Improved: The macro renderers cannot compile a string as a 
template anymore (OFBIZ-13682)
4ccdb3e917 is described below

commit 4ccdb3e917b64245a527b1c01d8f9de613c6c714
Author: Mridul Pathak <[email protected]>
AuthorDate: Fri Oct 16 18:42:30 2026 +0530

    Improved: The macro renderers cannot compile a string as a template anymore 
(OFBIZ-13682)
    
    All the macro calls of the widget renderers are now made with data, so the 
code that compiled text as a template is not used anymore and is removed, which 
makes sure that a value can never be parsed as template code again. FtlWriter 
only compiles the constant text that calls a macro, and a test checks that no 
other class builds macro text or compiles a template, and that FtlWriter 
compiles that one constant only.
    
    - FtlWriter.processFtlString, MacroFormRenderer.executeMacro, 
MacroFormRenderer.encode with its escaping for a FreeMarker string literal, 
MacroCommonRenderer.escapeFtlString and the unused 
MacroFormRenderer.appendContentUrl are removed, as well as a macro call as text 
that RenderableFtlFormElementsBuilder built and never used.
    - RenderableFtlString is renamed RenderableFtlVerbatim, because FtlWriter 
writes it as it is and does not process it as a template.
---
 .../ofbiz/widget/renderer/macro/FtlWriter.java     | 35 ++------
 .../widget/renderer/macro/MacroCommonRenderer.java | 11 ---
 .../widget/renderer/macro/MacroFormRenderer.java   | 38 ---------
 .../macro/RenderableFtlFormElementsBuilder.java    | 16 ++--
 ...leFtlString.java => RenderableFtlVerbatim.java} | 30 +++----
 .../macro/renderable/RenderableFtlVisitor.java     |  2 +-
 .../ofbiz/widget/renderer/macro/FtlWriterTest.java |  6 +-
 .../macro/MacroRenderersTemplateTextGuardTest.java | 98 ++++++++++++++++++++++
 .../RenderableFtlFormElementsBuilderTest.java      |  8 +-
 9 files changed, 135 insertions(+), 109 deletions(-)

diff --git 
a/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/FtlWriter.java
 
b/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/FtlWriter.java
index 8261efbd8b..0a960ae699 100644
--- 
a/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/FtlWriter.java
+++ 
b/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/FtlWriter.java
@@ -30,19 +30,18 @@ import org.apache.ofbiz.widget.renderer.VisualTheme;
 import org.apache.ofbiz.widget.renderer.macro.renderable.RenderableFtl;
 import 
org.apache.ofbiz.widget.renderer.macro.renderable.RenderableFtlMacroCall;
 import org.apache.ofbiz.widget.renderer.macro.renderable.RenderableFtlNoop;
-import org.apache.ofbiz.widget.renderer.macro.renderable.RenderableFtlString;
+import org.apache.ofbiz.widget.renderer.macro.renderable.RenderableFtlVerbatim;
 import org.apache.ofbiz.widget.renderer.macro.renderable.RenderableFtlVisitor;
 
 import java.io.IOException;
-import java.io.Reader;
 import java.io.StringReader;
 import java.util.Locale;
 import java.util.Map;
-import java.util.UUID;
 import java.util.WeakHashMap;
 
 /**
- * Processes FTL templates and writes result to Appendables.
+ * Calls the macros of a macro library and writes the result to Appendables. 
The only template text it compiles is the
+ * constant text that calls a macro, so no value of the data is ever parsed as 
template code.
  */
 public final class FtlWriter {
     private static final String MODULE = FtlWriter.class.getName();
@@ -90,26 +89,6 @@ public final class FtlWriter {
         renderableFtl.accept(writerVisitor, locale);
     }
 
-    /**
-     * Process the given FTL string as a template and write the result to the 
Appendable.
-     *
-     * @param writer    The Appendable to write the result of the template 
processing to.
-     * @param ftlString The FTL string to process as a template.
-     */
-    public void processFtlString(Appendable writer, Locale locale, String 
ftlString) {
-        try {
-            final Environment environment = getEnvironment(writer, locale);
-            setThemeVariables(environment);
-            Reader templateReader = new StringReader(ftlString);
-            Template template = new Template(UUID.randomUUID().toString(), 
templateReader,
-                    FreeMarkerWorker.getDefaultOfbizConfig());
-            templateReader.close();
-            environment.include(template);
-        } catch (TemplateException | IOException e) {
-            Debug.logError(e, "Error rendering ftl, ftlString: " + ftlString, 
MODULE);
-        }
-    }
-
     /**
      * Call a macro of the macro library and write the result to the 
Appendable. The name of the macro and the
      * parameters are passed to FreeMarker as data, never as template text.
@@ -173,7 +152,7 @@ public final class FtlWriter {
 
     /**
      * Visitor for RenderableFtl objects that will call the macros of the 
macro library, or write the finished output
-     * of a RenderableFtlString as it is, to the configured Appendable.
+     * of a RenderableFtlVerbatim as it is, to the configured Appendable.
      */
     private final class RenderableFtlFtlWriterVisitor implements 
RenderableFtlVisitor {
 
@@ -198,12 +177,12 @@ public final class FtlWriter {
         }
 
         @Override
-        public void visit(RenderableFtlString renderableFtlString, Locale 
locale) {
+        public void visit(RenderableFtlVerbatim renderableFtlVerbatim, Locale 
locale) {
             // the string holds finished output that can contain data, so it 
is written as it is and never compiled
             try {
-                writer.append(renderableFtlString.getFtlString());
+                writer.append(renderableFtlVerbatim.getOutput());
             } catch (IOException e) {
-                Debug.logError(e, "Error writing the output of a 
RenderableFtlString", MODULE);
+                Debug.logError(e, "Error writing the output of a 
RenderableFtlVerbatim", MODULE);
             }
         }
     }
diff --git 
a/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/MacroCommonRenderer.java
 
b/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/MacroCommonRenderer.java
index 060fcb1d6a..8be5547151 100644
--- 
a/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/MacroCommonRenderer.java
+++ 
b/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/MacroCommonRenderer.java
@@ -38,17 +38,6 @@ import org.apache.ofbiz.widget.model.ModelForm;
 
 public class MacroCommonRenderer {
 
-    /**
-     * Escape a value so that it is rendered literally when it is placed 
inside a double-quoted FreeMarker
-     * string literal of a macro call: the backslash and the double quote are 
escaped, and so is the opening
-     * brace, so that neither <code>${...}</code> nor <code>#{...}</code> is 
interpolated.
-     * @param value the value to escape, may be null
-     * @return the escaped value, or an empty string if the value is null
-     */
-    public static String escapeFtlString(String value) {
-        return value == null ? "" : value.replace("\\", "\\\\").replace("\"", 
"\\\"").replace("{", "\\{");
-    }
-
     /**
      * Create an ajaxXxxx JavaScript CSV string from a list of UpdateArea 
objects. See
      * <code>OfbizUtil.js</code>.
diff --git 
a/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/MacroFormRenderer.java
 
b/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/MacroFormRenderer.java
index 94ee7be6e6..b79ab21049 100644
--- 
a/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/MacroFormRenderer.java
+++ 
b/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/MacroFormRenderer.java
@@ -38,7 +38,6 @@ import org.apache.ofbiz.base.util.UtilValidate;
 import org.apache.ofbiz.base.util.string.FlexibleStringExpander;
 import org.apache.ofbiz.entity.Delegator;
 import org.apache.ofbiz.webapp.control.RequestHandler;
-import org.apache.ofbiz.webapp.taglib.ContentUrlTag;
 import org.apache.ofbiz.widget.WidgetWorker;
 import org.apache.ofbiz.widget.content.StaticContentUrlProvider;
 import org.apache.ofbiz.widget.model.CommonWidgetModels;
@@ -82,7 +81,6 @@ import 
org.apache.ofbiz.widget.renderer.macro.renderable.RenderableFtlMacroCall;
 public final class MacroFormRenderer implements FormStringRenderer {
 
     private static final String MODULE = MacroFormRenderer.class.getName();
-    private final UtilCodec.SimpleEncoder internalEncoder;
     private final RequestHandler rh;
     private final HttpServletRequest request;
     private final HttpServletResponse response;
@@ -106,7 +104,6 @@ public final class MacroFormRenderer implements 
FormStringRenderer {
         this.visualTheme = ThemeFactory.resolveVisualTheme(request);
         this.rh = RequestHandler.from(request);
         this.javaScriptEnabled = UtilHttp.isJavaScriptEnabled(request);
-        internalEncoder = UtilCodec.getEncoder("string");
         this.ftlWriter = ftlWriter != null ? ftlWriter : new 
FtlWriter(macroLibraryPath, this.visualTheme);
         final StaticContentUrlProvider staticContentUrlProvider = new 
StaticContentUrlProvider(request);
         this.renderableFtlFormElementsBuilder = 
renderableFtlFormElementsBuilder != null
@@ -130,34 +127,6 @@ public final class MacroFormRenderer implements 
FormStringRenderer {
         ftlWriter.processFtl(writer, locale, renderableFtl);
     }
 
-    private void executeMacro(Appendable writer, String macro) {
-        ftlWriter.processFtlString(writer, null, macro);
-    }
-
-    /**
-     * Make locale available before executing macro
-     * @param writer
-     * @param locale
-     * @param macro
-     */
-    @SuppressWarnings("unused")
-    private void executeMacro(Appendable writer, Locale locale, String macro) {
-        ftlWriter.processFtlString(writer, locale, macro);
-    }
-
-    private String encode(String value, ModelFormField modelFormField, 
Map<String, Object> context) {
-        if (UtilValidate.isEmpty(value)) {
-            return value;
-        }
-        UtilCodec.SimpleEncoder encoder = (UtilCodec.SimpleEncoder) 
context.get("simpleEncoder");
-        if (modelFormField.getEncodeOutput() && encoder != null) {
-            value = encoder.encode(value);
-        } else {
-            value = internalEncoder.encode(value);
-        }
-        return value;
-    }
-
     private String encodeForMacro(String value, ModelFormField modelFormField, 
Map<String, Object> context) {
         if (UtilValidate.isEmpty(value)) {
             return value;
@@ -803,13 +772,6 @@ public final class MacroFormRenderer implements 
FormStringRenderer {
         writeFtlElement(writer, renderableFtl);
     }
 
-    public void appendContentUrl(Appendable writer, String location) throws 
IOException {
-        StringBuilder buffer = new StringBuilder();
-        ContentUrlTag.appendContentPrefix(this.request, buffer);
-        writer.append(buffer.toString());
-        writer.append(location);
-    }
-
     @Override
     public void renderContainerFindField(Appendable writer, Map<String, 
Object> context, ContainerField containerField) throws IOException {
         final RenderableFtlMacroCall containerMc = 
renderableFtlFormElementsBuilder.containerMacroCall(context, containerField);
diff --git 
a/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/RenderableFtlFormElementsBuilder.java
 
b/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/RenderableFtlFormElementsBuilder.java
index b83a0766e0..cebabbe491 100644
--- 
a/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/RenderableFtlFormElementsBuilder.java
+++ 
b/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/RenderableFtlFormElementsBuilder.java
@@ -75,8 +75,8 @@ import 
org.apache.ofbiz.widget.renderer.macro.renderable.RenderableFtlMacroCall;
 import 
org.apache.ofbiz.widget.renderer.macro.renderable.RenderableFtlMacroCall.RenderableFtlMacroCallBuilder;
 import org.apache.ofbiz.widget.renderer.macro.renderable.RenderableFtlNoop;
 import org.apache.ofbiz.widget.renderer.macro.renderable.RenderableFtlSequence;
-import org.apache.ofbiz.widget.renderer.macro.renderable.RenderableFtlString;
-import 
org.apache.ofbiz.widget.renderer.macro.renderable.RenderableFtlString.RenderableFtlStringBuilder;
+import org.apache.ofbiz.widget.renderer.macro.renderable.RenderableFtlVerbatim;
+import 
org.apache.ofbiz.widget.renderer.macro.renderable.RenderableFtlVerbatim.RenderableFtlVerbatimBuilder;
 import org.jsoup.nodes.Element;
 
 /**
@@ -170,8 +170,6 @@ public final class RenderableFtlFormElementsBuilder {
                 .stringParameter("class", modelFormField.getWidgetStyle())
                 .stringParameter("alert", modelFormField.shouldBeRed(context) 
? "true" : "false");
 
-        StringWriter sr = new StringWriter();
-        sr.append("<@renderDisplayField ");
         if (ajaxEnabled) {
             String url = inPlaceEditor.getUrl(context);
             StringBuffer extraParameterBuffer = new StringBuffer();
@@ -827,8 +825,8 @@ public final class RenderableFtlFormElementsBuilder {
             parameterMap.put(viewIndexField, Integer.toString(viewIndex));
             parameterMap.put(viewSizeField, Integer.toString(viewSize));
 
-            final RenderableFtlStringBuilder renderableFtlStringBuilder = 
RenderableFtlString.builder();
-            final StringBuilder htmlStringBuilder = 
renderableFtlStringBuilder.getStringBuilder();
+            final RenderableFtlVerbatimBuilder renderableFtlVerbatimBuilder = 
RenderableFtlVerbatim.builder();
+            final StringBuilder htmlStringBuilder = 
renderableFtlVerbatimBuilder.getStringBuilder();
 
             if ("multi".equals(modelForm.getType())) {
                 final Element anchorElement = 
WidgetWorker.makeHiddenFormLinkAnchorElement(linkStyle,
@@ -839,12 +837,12 @@ public final class RenderableFtlFormElementsBuilder {
 
                 // this is a bit trickier, since we can't do a nested form 
we'll have to put the link to submit the
                 // form in place, but put the actual form def elsewhere, ie 
after the big form is closed
-                final RenderableFtlString postFormRenderableFtlString = 
RenderableFtlString.withStringBuilder(sb -> {
+                final RenderableFtlVerbatim postFormRenderableFtlVerbatim = 
RenderableFtlVerbatim.withStringBuilder(sb -> {
                     final Element hiddenFormElement = 
WidgetWorker.makeHiddenFormLinkFormElement(target, targetType,
                             targetWindow, parameterMap, modelFormField, 
request, response, context);
                     sb.append(hiddenFormElement.outerHtml());
                 });
-                appendToPostFormRenderableFtl(postFormRenderableFtlString, 
context);
+                appendToPostFormRenderableFtl(postFormRenderableFtlVerbatim, 
context);
 
             } else {
                 final Element hiddenFormElement = 
WidgetWorker.makeHiddenFormLinkFormElement(target, targetType,
@@ -857,7 +855,7 @@ public final class RenderableFtlFormElementsBuilder {
                 }
             }
 
-            return renderableFtlStringBuilder.build();
+            return renderableFtlVerbatimBuilder.build();
 
         } else {
             if ("layered-modal".equals(realLinkType)) {
diff --git 
a/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/renderable/RenderableFtlString.java
 
b/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/renderable/RenderableFtlVerbatim.java
similarity index 69%
rename from 
framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/renderable/RenderableFtlString.java
rename to 
framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/renderable/RenderableFtlVerbatim.java
index eb251ed9f5..0b290a7144 100644
--- 
a/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/renderable/RenderableFtlString.java
+++ 
b/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/renderable/RenderableFtlVerbatim.java
@@ -25,31 +25,31 @@ import java.util.function.Consumer;
  * Finished output, such as the HTML of a link, that is written as it is. It 
is not a template: nothing in it is
  * interpreted, so it can hold values of the data.
  */
-public final class RenderableFtlString implements RenderableFtl {
-    private final String ftlString;
+public final class RenderableFtlVerbatim implements RenderableFtl {
+    private final String output;
 
-    public RenderableFtlString(final String ftlString) {
-        this.ftlString = ftlString;
+    public RenderableFtlVerbatim(final String output) {
+        this.output = output;
     }
 
-    public String getFtlString() {
-        return ftlString;
+    public String getOutput() {
+        return output;
     }
 
-    public static RenderableFtlStringBuilder builder() {
-        return new RenderableFtlStringBuilder();
+    public static RenderableFtlVerbatimBuilder builder() {
+        return new RenderableFtlVerbatimBuilder();
     }
 
-    public static RenderableFtlString withStringBuilder(final 
Consumer<StringBuilder> callback) {
-        final RenderableFtlStringBuilder builder = builder();
+    public static RenderableFtlVerbatim withStringBuilder(final 
Consumer<StringBuilder> callback) {
+        final RenderableFtlVerbatimBuilder builder = builder();
         callback.accept(builder.getStringBuilder());
         return builder.build();
     }
 
     @Override
     public String toString() {
-        return "RenderableFtlString{"
-                + "ftlString='" + ftlString + '\''
+        return "RenderableFtlVerbatim{"
+                + "output='" + output + '\''
                 + '}';
     }
 
@@ -58,15 +58,15 @@ public final class RenderableFtlString implements 
RenderableFtl {
         visitor.visit(this, locale);
     }
 
-    public static final class RenderableFtlStringBuilder {
+    public static final class RenderableFtlVerbatimBuilder {
         private final StringBuilder stringBuilder = new StringBuilder();
 
         public StringBuilder getStringBuilder() {
             return stringBuilder;
         }
 
-        public RenderableFtlString build() {
-            return new RenderableFtlString(stringBuilder.toString());
+        public RenderableFtlVerbatim build() {
+            return new RenderableFtlVerbatim(stringBuilder.toString());
         }
     }
 }
diff --git 
a/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/renderable/RenderableFtlVisitor.java
 
b/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/renderable/RenderableFtlVisitor.java
index 6dd65d9bcd..d6bcc7f941 100644
--- 
a/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/renderable/RenderableFtlVisitor.java
+++ 
b/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/renderable/RenderableFtlVisitor.java
@@ -23,5 +23,5 @@ import java.util.Locale;
 public interface RenderableFtlVisitor {
     void visit(RenderableFtlMacroCall renderableFtlMacroCall, Locale locale);
     void visit(RenderableFtlNoop renderableFtlNoop, Locale locale);
-    void visit(RenderableFtlString renderableFtlString, Locale locale);
+    void visit(RenderableFtlVerbatim renderableFtlVerbatim, Locale locale);
 }
diff --git 
a/framework/widget/src/test/java/org/apache/ofbiz/widget/renderer/macro/FtlWriterTest.java
 
b/framework/widget/src/test/java/org/apache/ofbiz/widget/renderer/macro/FtlWriterTest.java
index e51d501c76..3e6737ec1e 100644
--- 
a/framework/widget/src/test/java/org/apache/ofbiz/widget/renderer/macro/FtlWriterTest.java
+++ 
b/framework/widget/src/test/java/org/apache/ofbiz/widget/renderer/macro/FtlWriterTest.java
@@ -32,7 +32,7 @@ import static org.mockito.Mockito.verify;
 import java.io.StringWriter;
 
 import org.apache.ofbiz.base.util.template.FreeMarkerWorker;
-import org.apache.ofbiz.widget.renderer.macro.renderable.RenderableFtlString;
+import org.apache.ofbiz.widget.renderer.macro.renderable.RenderableFtlVerbatim;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -61,12 +61,12 @@ public final class FtlWriterTest {
     }
 
     @Test
-    public void renderableFtlStringIsWrittenAsItIsAndNeverCompiled() throws 
Exception {
+    public void renderableFtlVerbatimIsWrittenAsItIsAndNeverCompiled() throws 
Exception {
         final FtlWriter ftlWriter = new FtlWriter("LIBRARY");
         final StringWriter writer = new StringWriter();
         final String html = "<a href=\"#\">${1+1} <#stop \"stopped\"> 
#{2}</a>";
 
-        ftlWriter.processFtl(writer, null, new RenderableFtlString(html));
+        ftlWriter.processFtl(writer, null, new RenderableFtlVerbatim(html));
 
         assertThat(writer.toString(), equalTo(html));
         // a compiled string would have been included in the environment of 
the writer
diff --git 
a/framework/widget/src/test/java/org/apache/ofbiz/widget/renderer/macro/MacroRenderersTemplateTextGuardTest.java
 
b/framework/widget/src/test/java/org/apache/ofbiz/widget/renderer/macro/MacroRenderersTemplateTextGuardTest.java
new file mode 100644
index 0000000000..194239cecc
--- /dev/null
+++ 
b/framework/widget/src/test/java/org/apache/ofbiz/widget/renderer/macro/MacroRenderersTemplateTextGuardTest.java
@@ -0,0 +1,98 @@
+/*******************************************************************************
+ * 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.widget.renderer.macro;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.empty;
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Guards the rule that the macro renderers pass the values to the macros as 
data and never turn them into template
+ * text: the only template text that is compiled is the constant text of 
{@link FtlWriter} that calls a macro.
+ */
+public final class MacroRenderersTemplateTextGuardTest {
+
+    private static final String MACRO_PACKAGE_PATH = 
"org/apache/ofbiz/widget/renderer/macro";
+    private static final String FTL_WRITER = "FtlWriter.java";
+
+    private static Path macroSources() {
+        for (final String root : new String[] 
{"framework/widget/src/main/java/", "src/main/java/"}) {
+            final Path path = Paths.get(root + MACRO_PACKAGE_PATH);
+            if (Files.isDirectory(path)) {
+                return path;
+            }
+        }
+        return null;
+    }
+
+    private static List<Path> javaFiles(final Path directory) throws 
IOException {
+        try (Stream<Path> files = Files.walk(directory)) {
+            return files.filter(file -> 
file.toString().endsWith(".java")).collect(Collectors.toList());
+        }
+    }
+
+    private static String read(final Path file) throws IOException {
+        return new String(Files.readAllBytes(file), StandardCharsets.UTF_8);
+    }
+
+    @Test
+    public void onlyFtlWriterBuildsMacroCallsOrCompilesTemplates() throws 
IOException {
+        final Path sources = macroSources();
+        assumeTrue(sources != null, "the sources of the macro renderers are 
not available from the working directory");
+
+        final List<String> offenders = new ArrayList<>();
+        for (final Path file : javaFiles(sources)) {
+            final String name = file.getFileName().toString();
+            if (FTL_WRITER.equals(name)) {
+                continue;
+            }
+            final String content = read(file);
+            if (content.contains("\"<@")) {
+                offenders.add(name + " builds a macro call as text");
+            }
+            if (content.contains("new Template(")) {
+                offenders.add(name + " compiles a template");
+            }
+        }
+        assertThat(offenders, empty());
+    }
+
+    @Test
+    public void ftlWriterCompilesOnlyTheConstantMacroCallTemplate() throws 
IOException {
+        final Path sources = macroSources();
+        assumeTrue(sources != null, "the sources of the macro renderers are 
not available from the working directory");
+
+        final String content = read(sources.resolve(FTL_WRITER));
+        assertThat("FtlWriter compiles a single template", content.split("new 
Template\\(", -1).length - 1, equalTo(1));
+        assertThat(content.contains("MACRO_CALL_TEMPLATE"), equalTo(true));
+    }
+}
diff --git 
a/framework/widget/src/test/java/org/apache/ofbiz/widget/renderer/macro/RenderableFtlFormElementsBuilderTest.java
 
b/framework/widget/src/test/java/org/apache/ofbiz/widget/renderer/macro/RenderableFtlFormElementsBuilderTest.java
index 6d63f6699f..d7c29bcb5a 100644
--- 
a/framework/widget/src/test/java/org/apache/ofbiz/widget/renderer/macro/RenderableFtlFormElementsBuilderTest.java
+++ 
b/framework/widget/src/test/java/org/apache/ofbiz/widget/renderer/macro/RenderableFtlFormElementsBuilderTest.java
@@ -33,7 +33,7 @@ import org.apache.ofbiz.widget.renderer.VisualTheme;
 import 
org.apache.ofbiz.widget.renderer.macro.renderable.RenderableFtlMacroCall;
 import org.apache.ofbiz.widget.renderer.macro.renderable.RenderableFtl;
 import org.apache.ofbiz.widget.renderer.macro.renderable.RenderableFtlNoop;
-import org.apache.ofbiz.widget.renderer.macro.renderable.RenderableFtlString;
+import org.apache.ofbiz.widget.renderer.macro.renderable.RenderableFtlVerbatim;
 import org.mockito.MockedStatic;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -1068,9 +1068,9 @@ public final class RenderableFtlFormElementsBuilderTest {
         final RenderableFtl link = 
renderableFtlFormElementsBuilder.makeHyperlinkByType("hidden-form", "STYLE", 
null, "#",
                 new HashMap<>(), "A ${description}", "", "", modelFormField, 
request, response, context);
 
-        assertThat(link instanceof RenderableFtlString, equalTo(true));
-        assertThat(((RenderableFtlString) link).getFtlString().contains("A 
${description}"), equalTo(true));
-        
assertThat(renderableFtlFormElementsBuilder.postMultiFormContent(context) 
instanceof RenderableFtlString, equalTo(true));
+        assertThat(link instanceof RenderableFtlVerbatim, equalTo(true));
+        assertThat(((RenderableFtlVerbatim) link).getOutput().contains("A 
${description}"), equalTo(true));
+        
assertThat(renderableFtlFormElementsBuilder.postMultiFormContent(context) 
instanceof RenderableFtlVerbatim, equalTo(true));
     }
 
     private static void labelsReturningTheirKey(final 
MockedStatic<UtilProperties> utilProperties) {

Reply via email to