You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ofbiz.apache.org by da...@apache.org on 2021/07/21 13:46:28 UTC

[ofbiz-framework] branch OFBIZ-12274-ftl-with-args created (now 440af94)

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

danwatford pushed a change to branch OFBIZ-12274-ftl-with-args
in repository https://gitbox.apache.org/repos/asf/ofbiz-framework.git.


      at 440af94  Improved: Use FTL with_args on macro calls (OFBIZ-12274)

This branch includes the following new commits:

     new 440af94  Improved: Use FTL with_args on macro calls (OFBIZ-12274)

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


[ofbiz-framework] 01/01: Improved: Use FTL with_args on macro calls (OFBIZ-12274)

Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

danwatford pushed a commit to branch OFBIZ-12274-ftl-with-args
in repository https://gitbox.apache.org/repos/asf/ofbiz-framework.git

commit 440af9473683b557768d8ad3e0b277eea4db4652
Author: Daniel Watford <da...@watfordconsulting.com>
AuthorDate: Wed Jul 21 14:39:36 2021 +0100

    Improved: Use FTL with_args on macro calls (OFBIZ-12274)
    
    Previous approach to rendering FTL macros was to encode all macro
    arguments as a single string and pass to the FTL processor.
    
    New approach is to use FTL's default wrapper to handle passing Java
    types to the FTL processor and referencing them from macro calls.
    
    Thanks: Xin Wang for introducing FTL with_args approach and providing
    POC.
---
 .../ofbiz/widget/renderer/macro/FtlWriter.java     | 74 ++++++++++++++++++----
 .../macro/RenderableFtlFormElementsBuilder.java    | 26 ++------
 .../parameter/MacroCallParameterBooleanValue.java  | 36 -----------
 .../parameter/MacroCallParameterMapValue.java      | 48 --------------
 .../parameter/MacroCallParameterStringValue.java   | 36 -----------
 .../renderer/macro/renderable/RenderableFtl.java   |  4 +-
 .../macro/renderable/RenderableFtlMacroCall.java   | 33 +++-------
 .../macro/renderable/RenderableFtlNoop.java        |  4 +-
 .../macro/renderable/RenderableFtlSequence.java    |  7 +-
 .../macro/renderable/RenderableFtlString.java      |  4 +-
 .../RenderableFtlVisitor.java}                     |  8 ++-
 .../MacroCallParameterBooleanValueMatcher.java     | 15 ++---
 .../macro/MacroCallParameterMapValueMatcher.java   | 15 ++---
 .../renderer/macro/MacroCallParameterMatcher.java  | 11 ++--
 .../MacroCallParameterStringValueMatcher.java      | 15 ++---
 15 files changed, 109 insertions(+), 227 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 744a9c5..542301b 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
@@ -18,6 +18,21 @@
  *******************************************************************************/
 package org.apache.ofbiz.widget.renderer.macro;
 
+import freemarker.core.Environment;
+import freemarker.template.DefaultObjectWrapper;
+import freemarker.template.DefaultObjectWrapperBuilder;
+import freemarker.template.Template;
+import freemarker.template.TemplateException;
+import org.apache.ofbiz.base.util.Debug;
+import org.apache.ofbiz.base.util.UtilMisc;
+import org.apache.ofbiz.base.util.template.FreeMarkerWorker;
+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.RenderableFtlVisitor;
+
 import java.io.IOException;
 import java.io.Reader;
 import java.io.StringReader;
@@ -26,22 +41,13 @@ import java.util.Map;
 import java.util.UUID;
 import java.util.WeakHashMap;
 
-import org.apache.ofbiz.base.util.Debug;
-import org.apache.ofbiz.base.util.UtilMisc;
-import org.apache.ofbiz.base.util.template.FreeMarkerWorker;
-import org.apache.ofbiz.widget.renderer.VisualTheme;
-import org.apache.ofbiz.widget.renderer.macro.renderable.RenderableFtl;
-
-import freemarker.core.Environment;
-import freemarker.template.Template;
-import freemarker.template.TemplateException;
-
 /**
  * Processes FTL templates and writes result to Appendables.
  */
 public final class FtlWriter {
     private static final String MODULE = FtlWriter.class.getName();
 
+    private final DefaultObjectWrapper defaultObjectWrapper = new DefaultObjectWrapperBuilder(FreeMarkerWorker.VERSION).build();
     private final WeakHashMap<Appendable, Environment> environments = new WeakHashMap<>();
     private final Template macroLibrary;
     private final VisualTheme visualTheme;
@@ -52,13 +58,14 @@ public final class FtlWriter {
     }
 
     /**
-     * Process the given RenderableFTL as a template and write the result to the Appendable.
+     * Process the given RenderableFtl as a template and write the result to the Appendable.
      *
      * @param writer        The Appendable to write the result of the template processing to.
      * @param renderableFtl The Renderable FTL to process as a template.
      */
     public void processFtl(final Appendable writer, final RenderableFtl renderableFtl) {
-        processFtlString(writer, null, renderableFtl.toFtlString());
+        final RenderableFtlFtlWriterVisitor writerVisitor = new RenderableFtlFtlWriterVisitor(writer);
+        renderableFtl.accept(writerVisitor);
     }
 
     /**
@@ -97,4 +104,47 @@ public final class FtlWriter {
         }
         return environment;
     }
+
+    /**
+     * Visitor for RenderableFtl objects that will process the contents of the objects as FTL templates and write the
+     * results to the configured Appendable.
+     */
+    private final class RenderableFtlFtlWriterVisitor implements RenderableFtlVisitor {
+
+        private final Appendable writer;
+
+        /**
+         * Construct the visitor, specifying the writer that FTL should be rendered to.
+         * @param writer The Appendable to write the result of the template processing to.
+         */
+        private RenderableFtlFtlWriterVisitor(final Appendable writer) {
+            this.writer = writer;
+        }
+
+        @Override
+        public void visit(RenderableFtlMacroCall renderableFtlMacroCall) {
+            final String name = renderableFtlMacroCall.getName();
+
+            try {
+                final Environment environment = getEnvironment(writer, null);
+
+                environment.setVariable("$args$" + name,
+                        defaultObjectWrapper.wrap(renderableFtlMacroCall.getParameters()));
+
+                processFtlString(writer, null, String.format("<@%s?with_args($args$%s)/>", name, name));
+            } catch (TemplateException | IOException e) {
+                Debug.logError(e, "Error rendering ftl macro: " + name, MODULE);
+            }
+        }
+
+        @Override
+        public void visit(RenderableFtlNoop renderableFtlNoop) {
+            // Do nothing.
+        }
+
+        @Override
+        public void visit(RenderableFtlString renderableFtlString) {
+            processFtlString(writer, null, renderableFtlString.getFtlString());
+        }
+    }
 }
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 f99c2e5..5bde105 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
@@ -31,9 +31,7 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpSession;
 
-import org.apache.commons.text.StringEscapeUtils;
 import org.apache.ofbiz.base.util.Debug;
-import org.apache.ofbiz.base.util.UtilCodec;
 import org.apache.ofbiz.base.util.UtilFormatOut;
 import org.apache.ofbiz.base.util.UtilGenerics;
 import org.apache.ofbiz.base.util.UtilHttp;
@@ -64,7 +62,6 @@ import org.jsoup.nodes.Element;
  */
 public final class RenderableFtlFormElementsBuilder {
     private static final String MODULE = RenderableFtlFormElementsBuilder.class.getName();
-    private final UtilCodec.SimpleEncoder internalEncoder = UtilCodec.getEncoder("string");
     private final VisualTheme visualTheme;
     private final RequestHandler requestHandler;
     private final HttpServletRequest request;
@@ -330,7 +327,6 @@ public final class RenderableFtlFormElementsBuilder {
                                              HttpServletRequest request, HttpServletResponse response,
                                              Map<String, Object> context) {
         String realLinkType = WidgetWorker.determineAutoLinkType(linkType, target, targetType, request);
-        String encodedDescription = encode(description, modelFormField, context);
         // get the parameterized pagination index and size fields
         int paginatorNumber = WidgetWorker.getPaginatorNumber(context);
         ModelForm modelForm = modelFormField.getModelForm();
@@ -354,7 +350,7 @@ public final class RenderableFtlFormElementsBuilder {
 
             if ("multi".equals(modelForm.getType())) {
                 final Element anchorElement = WidgetWorker.makeHiddenFormLinkAnchorElement(linkStyle,
-                        encodedDescription, confirmation, modelFormField, request, context);
+                        description, confirmation, modelFormField, request, context);
                 htmlStringBuilder.append(anchorElement.outerHtml());
 
                 // this is a bit trickier, since we can't do a nested form we'll have to put the link to submit the
@@ -371,7 +367,7 @@ public final class RenderableFtlFormElementsBuilder {
                         targetWindow, parameterMap, modelFormField, request, response, context);
                 htmlStringBuilder.append(hiddenFormElement.outerHtml());
                 final Element anchorElement = WidgetWorker.makeHiddenFormLinkAnchorElement(linkStyle,
-                        encodedDescription, confirmation, modelFormField, request, context);
+                        description, confirmation, modelFormField, request, context);
                 htmlStringBuilder.append(anchorElement.outerHtml());
             }
 
@@ -392,13 +388,13 @@ public final class RenderableFtlFormElementsBuilder {
                 }
                 request.setAttribute("uniqueItemName", uniqueItemName);
                 RenderableFtl renderableFtl = hyperlinkMacroCall(linkStyle, targetType, target, parameterMap,
-                        encodedDescription, confirmation, modelFormField, request, response, context, targetWindow);
+                        description, confirmation, modelFormField, request, response, context, targetWindow);
                 request.removeAttribute("uniqueItemName");
                 request.removeAttribute("height");
                 request.removeAttribute("width");
                 return renderableFtl;
             } else {
-                return hyperlinkMacroCall(linkStyle, targetType, target, parameterMap, encodedDescription, confirmation,
+                return hyperlinkMacroCall(linkStyle, targetType, target, parameterMap, description, confirmation,
                         modelFormField, request, response, context, targetWindow);
             }
         }
@@ -539,20 +535,6 @@ public final class RenderableFtlFormElementsBuilder {
         return macroCallBuilder.build();
     }
 
-    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");
-        boolean alreadyEncoded = value.equals(StringEscapeUtils.unescapeEcmaScript(StringEscapeUtils.unescapeHtml4(value)));
-        if (modelFormField.getEncodeOutput() && encoder != null && !alreadyEncoded) {
-            value = encoder.encode(value);
-        } else {
-            value = internalEncoder.encode(value);
-        }
-        return value;
-    }
-
     /**
      * 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/parameter/MacroCallParameterBooleanValue.java b/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/parameter/MacroCallParameterBooleanValue.java
deleted file mode 100644
index 3b6d0c3..0000000
--- a/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/parameter/MacroCallParameterBooleanValue.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*******************************************************************************
- * 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.parameter;
-
-public final class MacroCallParameterBooleanValue implements MacroCallParameterValue {
-    private final boolean value;
-
-    public MacroCallParameterBooleanValue(boolean value) {
-        this.value = value;
-    }
-
-    public boolean isValue() {
-        return value;
-    }
-
-    @Override
-    public String toFtlString() {
-        return Boolean.toString(value);
-    }
-}
diff --git a/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/parameter/MacroCallParameterMapValue.java b/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/parameter/MacroCallParameterMapValue.java
deleted file mode 100644
index 6b5f207..0000000
--- a/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/parameter/MacroCallParameterMapValue.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*******************************************************************************
- * 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.parameter;
-
-import org.apache.ofbiz.base.util.UtilValidate;
-
-import java.util.Map;
-import java.util.stream.Collectors;
-
-public final class MacroCallParameterMapValue implements MacroCallParameterValue {
-    private final Map<String, String> value;
-
-    public MacroCallParameterMapValue(Map<String, String> value) {
-        this.value = value;
-    }
-
-    public Map<String, String> getValue() {
-        return value;
-    }
-
-    @Override
-    public String toFtlString() {
-        if (UtilValidate.isNotEmpty(value)) {
-            return value.entrySet()
-                    .stream()
-                    .map(entry -> "'" + entry.getKey() + "':'" + entry.getValue() + "'")
-                    .collect(Collectors.joining(",", "\"{", "}\""));
-        } else {
-            return "\"\"";
-        }
-    }
-}
diff --git a/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/parameter/MacroCallParameterStringValue.java b/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/parameter/MacroCallParameterStringValue.java
deleted file mode 100644
index 1c0aaff..0000000
--- a/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/parameter/MacroCallParameterStringValue.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*******************************************************************************
- * 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.parameter;
-
-public final class MacroCallParameterStringValue implements MacroCallParameterValue {
-    private final String value;
-
-    public MacroCallParameterStringValue(String value) {
-        this.value = value;
-    }
-
-    public String getValue() {
-        return value;
-    }
-
-    @Override
-    public String toFtlString() {
-        return "\"" + value.replaceAll("\"", "\\\\\"") + "\"";
-    }
-}
diff --git a/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/renderable/RenderableFtl.java b/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/renderable/RenderableFtl.java
index 4b55ae4..bd34c33 100644
--- a/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/renderable/RenderableFtl.java
+++ b/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/renderable/RenderableFtl.java
@@ -19,8 +19,8 @@
 package org.apache.ofbiz.widget.renderer.macro.renderable;
 
 /**
- * Component that can be rendered to an FTL (FreeMarker Template Language) string.
+ * Represents an FTL element such as an FTL string or macro call.
  */
 public interface RenderableFtl {
-    String toFtlString();
+    void accept(RenderableFtlVisitor visitor);
 }
diff --git a/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/renderable/RenderableFtlMacroCall.java b/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/renderable/RenderableFtlMacroCall.java
index 03af7c4..b82070f 100644
--- a/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/renderable/RenderableFtlMacroCall.java
+++ b/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/renderable/RenderableFtlMacroCall.java
@@ -18,23 +18,17 @@
  *******************************************************************************/
 package org.apache.ofbiz.widget.renderer.macro.renderable;
 
-import org.apache.ofbiz.widget.renderer.macro.parameter.MacroCallParameterBooleanValue;
-import org.apache.ofbiz.widget.renderer.macro.parameter.MacroCallParameterMapValue;
-import org.apache.ofbiz.widget.renderer.macro.parameter.MacroCallParameterStringValue;
-import org.apache.ofbiz.widget.renderer.macro.parameter.MacroCallParameterValue;
-
 import java.util.HashMap;
 import java.util.Map;
-import java.util.stream.Collectors;
 
 /**
  * Represents an FTL macro call.
  */
 public final class RenderableFtlMacroCall implements RenderableFtl {
     private final String name;
-    private final Map<String, MacroCallParameterValue> parameters;
+    private final Map<String, Object> parameters;
 
-    private RenderableFtlMacroCall(String name, Map<String, MacroCallParameterValue> parameters) {
+    private RenderableFtlMacroCall(String name, Map<String, Object> parameters) {
         if (name == null) {
             throw new NullPointerException("RenderableFtlMacroCall name cannot be null");
         }
@@ -43,32 +37,25 @@ public final class RenderableFtlMacroCall implements RenderableFtl {
     }
 
     @Override
-    public String toFtlString() {
-        return parameters.entrySet()
-                .stream()
-                .map((entry) -> createFtlMacroParameter(entry.getKey(), entry.getValue()))
-                .collect(Collectors.joining(" ", "<@" + name + " ", " />"));
+    public void accept(final RenderableFtlVisitor visitor) {
+        visitor.visit(this);
     }
 
     public String getName() {
         return name;
     }
 
-    public Map<String, MacroCallParameterValue> getParameters() {
+    public Map<String, Object> getParameters() {
         return parameters;
     }
 
-    private String createFtlMacroParameter(final String parameterName, final MacroCallParameterValue parameterValue) {
-        return parameterName + "=" + parameterValue.toFtlString();
-    }
-
     public static RenderableFtlMacroCallBuilder builder() {
         return new RenderableFtlMacroCallBuilder();
     }
 
     public static final class RenderableFtlMacroCallBuilder {
         private String name;
-        private Map<String, MacroCallParameterValue> parameters = new HashMap<>();
+        private Map<String, Object> parameters = new HashMap<>();
 
         private RenderableFtlMacroCallBuilder() {
         }
@@ -79,22 +66,22 @@ public final class RenderableFtlMacroCall implements RenderableFtl {
         }
 
         public RenderableFtlMacroCallBuilder stringParameter(final String parameterName, final String parameterValue) {
-            return parameter(parameterName, new MacroCallParameterStringValue(parameterValue));
+            return parameter(parameterName, parameterValue);
         }
 
         public RenderableFtlMacroCallBuilder booleanParameter(final String parameterName, final boolean parameterValue) {
-            return parameter(parameterName, new MacroCallParameterBooleanValue(parameterValue));
+            return parameter(parameterName, parameterValue);
         }
 
         public RenderableFtlMacroCallBuilder mapParameter(final String parameterName, final Map<String, String> parameterValue) {
-            return parameter(parameterName, new MacroCallParameterMapValue(parameterValue));
+            return parameter(parameterName, parameterValue);
         }
 
         public RenderableFtlMacroCall build() {
             return new RenderableFtlMacroCall(name, parameters);
         }
 
-        private RenderableFtlMacroCallBuilder parameter(final String name, final MacroCallParameterValue value) {
+        private RenderableFtlMacroCallBuilder parameter(final String name, final Object value) {
             parameters.put(name, value);
             return this;
         }
diff --git a/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/renderable/RenderableFtlNoop.java b/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/renderable/RenderableFtlNoop.java
index a0d3ca8..7796748 100644
--- a/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/renderable/RenderableFtlNoop.java
+++ b/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/renderable/RenderableFtlNoop.java
@@ -25,7 +25,7 @@ public final class RenderableFtlNoop implements RenderableFtl {
     }
 
     @Override
-    public String toFtlString() {
-        return "";
+    public void accept(final RenderableFtlVisitor visitor) {
+        visitor.visit(this);
     }
 }
diff --git a/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/renderable/RenderableFtlSequence.java b/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/renderable/RenderableFtlSequence.java
index a575c43..85f72b7 100644
--- a/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/renderable/RenderableFtlSequence.java
+++ b/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/renderable/RenderableFtlSequence.java
@@ -20,7 +20,6 @@ package org.apache.ofbiz.widget.renderer.macro.renderable;
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.stream.Collectors;
 
 /**
  * Class for composing multiple RenderableFtl objects in a sequence.
@@ -33,10 +32,8 @@ public final class RenderableFtlSequence implements RenderableFtl {
     }
 
     @Override
-    public String toFtlString() {
-        return renderableFtls.stream()
-                .map(RenderableFtl::toFtlString)
-                .collect(Collectors.joining());
+    public void accept(final RenderableFtlVisitor visitor) {
+        renderableFtls.forEach(renderableFtl -> renderableFtl.accept(visitor));
     }
 
     public static RenderableFtlSequenceBuilder builder() {
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/RenderableFtlString.java
index 4cead28..a9e7e65 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/RenderableFtlString.java
@@ -49,8 +49,8 @@ public final class RenderableFtlString implements RenderableFtl {
     }
 
     @Override
-    public String toFtlString() {
-        return ftlString;
+    public void accept(final RenderableFtlVisitor visitor) {
+        visitor.visit(this);
     }
 
     public static final class RenderableFtlStringBuilder {
diff --git a/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/parameter/MacroCallParameterValue.java b/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/renderable/RenderableFtlVisitor.java
similarity index 78%
rename from framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/parameter/MacroCallParameterValue.java
rename to framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/renderable/RenderableFtlVisitor.java
index 46bc1db..66aec95 100644
--- a/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/parameter/MacroCallParameterValue.java
+++ b/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/renderable/RenderableFtlVisitor.java
@@ -16,8 +16,10 @@
  * specific language governing permissions and limitations
  * under the License.
  *******************************************************************************/
-package org.apache.ofbiz.widget.renderer.macro.parameter;
+package org.apache.ofbiz.widget.renderer.macro.renderable;
 
-public interface MacroCallParameterValue {
-    String toFtlString();
+public interface RenderableFtlVisitor {
+    void visit(RenderableFtlMacroCall renderableFtlMacroCall);
+    void visit(RenderableFtlNoop renderableFtlNoop);
+    void visit(RenderableFtlString renderableFtlString);
 }
diff --git a/framework/widget/src/test/java/org/apache/ofbiz/widget/renderer/macro/MacroCallParameterBooleanValueMatcher.java b/framework/widget/src/test/java/org/apache/ofbiz/widget/renderer/macro/MacroCallParameterBooleanValueMatcher.java
index ceaf9cc6..2ab2769 100644
--- a/framework/widget/src/test/java/org/apache/ofbiz/widget/renderer/macro/MacroCallParameterBooleanValueMatcher.java
+++ b/framework/widget/src/test/java/org/apache/ofbiz/widget/renderer/macro/MacroCallParameterBooleanValueMatcher.java
@@ -18,23 +18,19 @@
  *******************************************************************************/
 package org.apache.ofbiz.widget.renderer.macro;
 
-import org.apache.ofbiz.widget.renderer.macro.parameter.MacroCallParameterBooleanValue;
-import org.apache.ofbiz.widget.renderer.macro.parameter.MacroCallParameterValue;
 import org.hamcrest.Description;
 import org.hamcrest.TypeSafeMatcher;
 
-public final class MacroCallParameterBooleanValueMatcher extends TypeSafeMatcher<MacroCallParameterValue> {
+public final class MacroCallParameterBooleanValueMatcher extends TypeSafeMatcher<Object> {
     private final boolean value;
 
     public MacroCallParameterBooleanValueMatcher(final boolean value) {
-        super(MacroCallParameterBooleanValue.class);
         this.value = value;
     }
 
     @Override
-    protected boolean matchesSafely(final MacroCallParameterValue item) {
-        final MacroCallParameterBooleanValue booleanValue = (MacroCallParameterBooleanValue) item;
-        return value == booleanValue.isValue();
+    protected boolean matchesSafely(final Object item) {
+        return item.equals(value);
     }
 
     @Override
@@ -43,8 +39,7 @@ public final class MacroCallParameterBooleanValueMatcher extends TypeSafeMatcher
     }
 
     @Override
-    protected void describeMismatchSafely(final MacroCallParameterValue item, final Description mismatchDescription) {
-        final MacroCallParameterBooleanValue booleanValue = (MacroCallParameterBooleanValue) item;
-        mismatchDescription.appendText("with boolean value '" + booleanValue.isValue() + "'");
+    protected void describeMismatchSafely(final Object item, final Description mismatchDescription) {
+        mismatchDescription.appendText("with boolean value '" + item + "'");
     }
 }
diff --git a/framework/widget/src/test/java/org/apache/ofbiz/widget/renderer/macro/MacroCallParameterMapValueMatcher.java b/framework/widget/src/test/java/org/apache/ofbiz/widget/renderer/macro/MacroCallParameterMapValueMatcher.java
index 90cb099..84e98f9 100644
--- a/framework/widget/src/test/java/org/apache/ofbiz/widget/renderer/macro/MacroCallParameterMapValueMatcher.java
+++ b/framework/widget/src/test/java/org/apache/ofbiz/widget/renderer/macro/MacroCallParameterMapValueMatcher.java
@@ -18,26 +18,22 @@
  *******************************************************************************/
 package org.apache.ofbiz.widget.renderer.macro;
 
-import org.apache.ofbiz.widget.renderer.macro.parameter.MacroCallParameterMapValue;
-import org.apache.ofbiz.widget.renderer.macro.parameter.MacroCallParameterValue;
 import org.hamcrest.Description;
 import org.hamcrest.Matcher;
 import org.hamcrest.TypeSafeMatcher;
 
 import java.util.Map;
 
-public final class MacroCallParameterMapValueMatcher extends TypeSafeMatcher<MacroCallParameterValue> {
+public final class MacroCallParameterMapValueMatcher extends TypeSafeMatcher<Object> {
     private final Matcher<Map<String, String>> matcher;
 
     public MacroCallParameterMapValueMatcher(final Matcher<Map<String, String>> matcher) {
-        super(MacroCallParameterMapValue.class);
         this.matcher = matcher;
     }
 
     @Override
-    protected boolean matchesSafely(final MacroCallParameterValue item) {
-        final MacroCallParameterMapValue mapValue = (MacroCallParameterMapValue) item;
-        return matcher.matches(mapValue.getValue());
+    protected boolean matchesSafely(final Object item) {
+        return matcher.matches(item);
     }
 
     @Override
@@ -48,10 +44,9 @@ public final class MacroCallParameterMapValueMatcher extends TypeSafeMatcher<Mac
     }
 
     @Override
-    protected void describeMismatchSafely(final MacroCallParameterValue item, final Description mismatchDescription) {
-        final MacroCallParameterMapValue mapValue = (MacroCallParameterMapValue) item;
+    protected void describeMismatchSafely(final Object item, final Description mismatchDescription) {
         mismatchDescription.appendText("with map value '");
-        matcher.describeMismatch(mapValue, mismatchDescription);
+        matcher.describeMismatch(item, mismatchDescription);
         mismatchDescription.appendText("' ");
     }
 }
diff --git a/framework/widget/src/test/java/org/apache/ofbiz/widget/renderer/macro/MacroCallParameterMatcher.java b/framework/widget/src/test/java/org/apache/ofbiz/widget/renderer/macro/MacroCallParameterMatcher.java
index 38ba735..d978f4e 100644
--- a/framework/widget/src/test/java/org/apache/ofbiz/widget/renderer/macro/MacroCallParameterMatcher.java
+++ b/framework/widget/src/test/java/org/apache/ofbiz/widget/renderer/macro/MacroCallParameterMatcher.java
@@ -18,27 +18,26 @@
  *******************************************************************************/
 package org.apache.ofbiz.widget.renderer.macro;
 
-import org.apache.ofbiz.widget.renderer.macro.parameter.MacroCallParameterValue;
 import org.hamcrest.Description;
 import org.hamcrest.Matcher;
 import org.hamcrest.TypeSafeMatcher;
 
 import java.util.Map;
 
-public final class MacroCallParameterMatcher extends TypeSafeMatcher<Map.Entry<String, MacroCallParameterValue>> {
+public final class MacroCallParameterMatcher extends TypeSafeMatcher<Map.Entry<String, Object>> {
     private final String name;
-    private final Matcher<MacroCallParameterValue> valueMatcher;
+    private final Matcher<Object> valueMatcher;
 
     private boolean nameMatches = true;
     private boolean valueMatches = true;
 
-    public MacroCallParameterMatcher(final String name, final Matcher<MacroCallParameterValue> valueMatcher) {
+    public MacroCallParameterMatcher(final String name, final Matcher<Object> valueMatcher) {
         this.name = name;
         this.valueMatcher = valueMatcher;
     }
 
     @Override
-    protected boolean matchesSafely(final Map.Entry<String, MacroCallParameterValue> item) {
+    protected boolean matchesSafely(final Map.Entry<String, Object> item) {
         if (name != null) {
             nameMatches = name.equals(item.getKey());
         }
@@ -62,7 +61,7 @@ public final class MacroCallParameterMatcher extends TypeSafeMatcher<Map.Entry<S
     }
 
     @Override
-    protected void describeMismatchSafely(final Map.Entry<String, MacroCallParameterValue> item,
+    protected void describeMismatchSafely(final Map.Entry<String, Object> item,
                                           final Description mismatchDescription) {
         if (name != null) {
             mismatchDescription.appendText("has name '" + item.getKey() + "' ");
diff --git a/framework/widget/src/test/java/org/apache/ofbiz/widget/renderer/macro/MacroCallParameterStringValueMatcher.java b/framework/widget/src/test/java/org/apache/ofbiz/widget/renderer/macro/MacroCallParameterStringValueMatcher.java
index 735c02f..ebe6368 100644
--- a/framework/widget/src/test/java/org/apache/ofbiz/widget/renderer/macro/MacroCallParameterStringValueMatcher.java
+++ b/framework/widget/src/test/java/org/apache/ofbiz/widget/renderer/macro/MacroCallParameterStringValueMatcher.java
@@ -18,23 +18,19 @@
  *******************************************************************************/
 package org.apache.ofbiz.widget.renderer.macro;
 
-import org.apache.ofbiz.widget.renderer.macro.parameter.MacroCallParameterStringValue;
-import org.apache.ofbiz.widget.renderer.macro.parameter.MacroCallParameterValue;
 import org.hamcrest.Description;
 import org.hamcrest.TypeSafeMatcher;
 
-public final class MacroCallParameterStringValueMatcher extends TypeSafeMatcher<MacroCallParameterValue> {
+public final class MacroCallParameterStringValueMatcher extends TypeSafeMatcher<Object> {
     private final String value;
 
     public MacroCallParameterStringValueMatcher(final String value) {
-        super(MacroCallParameterStringValue.class);
         this.value = value;
     }
 
     @Override
-    protected boolean matchesSafely(final MacroCallParameterValue item) {
-        final MacroCallParameterStringValue stringValue = (MacroCallParameterStringValue) item;
-        return value.equals(stringValue.getValue());
+    protected boolean matchesSafely(final Object item) {
+        return value.equals(item);
     }
 
     @Override
@@ -43,8 +39,7 @@ public final class MacroCallParameterStringValueMatcher extends TypeSafeMatcher<
     }
 
     @Override
-    protected void describeMismatchSafely(final MacroCallParameterValue item, final Description mismatchDescription) {
-        final MacroCallParameterStringValue stringValue = (MacroCallParameterStringValue) item;
-        mismatchDescription.appendText("with string value '" + stringValue.getValue() + "'");
+    protected void describeMismatchSafely(final Object item, final Description mismatchDescription) {
+        mismatchDescription.appendText("with string value '" + item + "'");
     }
 }