[ofbiz-framework] branch trunk updated: Improved: Headerize external script in multi-block html template (OFBIZ-11741)

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

[ofbiz-framework] branch trunk updated: Improved: Headerize external script in multi-block html template (OFBIZ-11741)

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

jamesyong 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 347ea63  Improved: Headerize external script in multi-block html template (OFBIZ-11741)
347ea63 is described below

commit 347ea63479d21db63653d0e52de60732d81654d7
Author: James Yong <[hidden email]>
AuthorDate: Sat Jun 6 10:23:41 2020 +0800

    Improved: Headerize external script in multi-block html template (OFBIZ-11741)
   
    Improve function for adding links to layoutSettings
---
 .../org/apache/ofbiz/widget/model/HtmlWidget.java  |  22 ++--
 .../ofbiz/widget/model/ModelScreenWidget.java      |   4 +-
 .../widget/model/MultiBlockHtmlTemplateUtil.java   | 139 ++++++++++++++++-----
 3 files changed, 126 insertions(+), 39 deletions(-)

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 b7a9cc9..1527be2 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
@@ -64,7 +64,8 @@ import freemarker.template.Version;
 public class HtmlWidget extends ModelScreenWidget {
     private static final String MODULE = HtmlWidget.class.getName();
 
-    private static final UtilCache<String, Template> specialTemplateCache = UtilCache.createUtilCache("widget.screen.template.ftl.general", 0, 0, false);
+    private static final UtilCache<String, Template> specialTemplateCache =
+            UtilCache.createUtilCache("widget.screen.template.ftl.general", 0, 0, false);
     protected static final Configuration specialConfig = FreeMarkerWorker.makeConfiguration(new ExtendedWrapper(FreeMarkerWorker.VERSION));
 
     // not sure if this is the best way to get FTL to use my fancy MapModel derivative, but should work at least...
@@ -140,7 +141,8 @@ public class HtmlWidget extends ModelScreenWidget {
     }
 
     @Override
-    public void renderWidgetString(Appendable writer, Map<String, Object> context, ScreenStringRenderer screenStringRenderer) throws GeneralException, IOException {
+    public void renderWidgetString(Appendable writer, Map<String, Object> context, ScreenStringRenderer screenStringRenderer)
+            throws GeneralException, IOException {
         for (ModelScreenWidget subWidget : subWidgets) {
             subWidget.renderWidgetString(writer, context, screenStringRenderer);
         }
@@ -181,7 +183,8 @@ public class HtmlWidget extends ModelScreenWidget {
         }
     }
 
-    public static void renderHtmlTemplateMultiBlock(Appendable writer, FlexibleStringExpander locationExdr, Map<String, Object> context) throws IOException {
+    public static void renderHtmlTemplateMultiBlock(Appendable writer, FlexibleStringExpander locationExdr,
+                                                    Map<String, Object> context) throws IOException {
         String location = locationExdr.expandString(context);
 
         StringWriter stringWriter = new StringWriter();
@@ -260,13 +263,13 @@ public class HtmlWidget extends ModelScreenWidget {
                     urls.add(origLoc);
                 } else {
                     try {
-                        urls = MultiBlockHtmlTemplateUtil.getHtmlImportsFromHtmlTemplate(origLoc);
+                        urls = MultiBlockHtmlTemplateUtil.getHtmlLinksFromHtmlTemplate(origLoc);
                     } catch (IOException e) {
                         String errMsg = "Error getting html imports from template at location [" + origLoc + "]: " + e.toString();
                         Debug.logError(e, errMsg, MODULE);
                     }
                 }
-                MultiBlockHtmlTemplateUtil.addLinksToHtmlImportCache(modelScreen.getSourceLocation(), modelScreen.getName(), urls);
+                MultiBlockHtmlTemplateUtil.addHtmlLinksToHtmlLinksForScreenCache(modelScreen.getSourceLocation(), modelScreen.getName(), urls);
             }
         }
 
@@ -306,7 +309,8 @@ public class HtmlWidget extends ModelScreenWidget {
             super(modelScreen, htmlTemplateDecoratorElement);
             this.locationExdr = FlexibleStringExpander.getInstance(htmlTemplateDecoratorElement.getAttribute("location"));
 
-            List<? extends Element> htmlTemplateDecoratorSectionElementList = UtilXml.childElementList(htmlTemplateDecoratorElement, "html-template-decorator-section");
+            List<? extends Element> htmlTemplateDecoratorSectionElementList = UtilXml.childElementList(
+                    htmlTemplateDecoratorElement, "html-template-decorator-section");
             for (Element htmlTemplateDecoratorSectionElement: htmlTemplateDecoratorSectionElementList) {
                 String name = htmlTemplateDecoratorSectionElement.getAttribute("name");
                 this.sectionMap.put(name, new HtmlTemplateDecoratorSection(modelScreen, htmlTemplateDecoratorSectionElement));
@@ -324,7 +328,8 @@ public class HtmlWidget extends ModelScreenWidget {
                 contextMs = UtilGenerics.cast(context);
             }
 
-            // create a standAloneStack, basically a "save point" for this SectionsRenderer, and make a new "screens" object just for it so it is isolated and doesn't follow the stack down
+            // create a standAloneStack, basically a "save point" for this SectionsRenderer,
+            // and make a new "screens" object just for it so it is isolated and doesn't follow the stack down
             MapStack<String> standAloneStack = contextMs.standAloneChildStack();
             standAloneStack.put("screens", new ScreenRenderer(writer, standAloneStack, screenStringRenderer));
             SectionsRenderer sections = new SectionsRenderer(this.sectionMap, standAloneStack, writer, screenStringRenderer);
@@ -361,7 +366,8 @@ public class HtmlWidget extends ModelScreenWidget {
         }
 
         @Override
-        public void renderWidgetString(Appendable writer, Map<String, Object> context, ScreenStringRenderer screenStringRenderer) throws GeneralException, IOException {
+        public void renderWidgetString(Appendable writer, Map<String, Object> context,
+                                       ScreenStringRenderer screenStringRenderer) throws GeneralException, IOException {
             // render sub-widgets
             renderSubWidgetsString(this.subWidgets, writer, context, screenStringRenderer);
         }
diff --git a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelScreenWidget.java b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelScreenWidget.java
index c34dfc5..e0914be 100644
--- a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelScreenWidget.java
+++ b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelScreenWidget.java
@@ -777,7 +777,7 @@ public abstract class ModelScreenWidget extends ModelWidget {
             this.nameExdr = FlexibleStringExpander.getInstance(includeScreenElement.getAttribute("name"));
             this.locationExdr = FlexibleStringExpander.getInstance(includeScreenElement.getAttribute("location"));
             this.shareScopeExdr = FlexibleStringExpander.getInstance(includeScreenElement.getAttribute("share-scope"));
-            MultiBlockHtmlTemplateUtil.collectChildScreenInfo(modelScreen, this.locationExdr.getOriginal(), this.nameExdr.getOriginal());
+            MultiBlockHtmlTemplateUtil.addChildScreen(modelScreen, this.locationExdr.getOriginal(), this.nameExdr.getOriginal());
         }
 
         @Override
@@ -873,7 +873,7 @@ public abstract class ModelScreenWidget extends ModelWidget {
                 sectionMap.put(decoratorSection.getName(), decoratorSection);
             }
             this.sectionMap = Collections.unmodifiableMap(sectionMap);
-            MultiBlockHtmlTemplateUtil.collectChildScreenInfo(modelScreen, this.locationExdr.getOriginal(), this.nameExdr.getOriginal());
+            MultiBlockHtmlTemplateUtil.addChildScreen(modelScreen, this.locationExdr.getOriginal(), this.nameExdr.getOriginal());
         }
 
         @Override
diff --git a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/MultiBlockHtmlTemplateUtil.java b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/MultiBlockHtmlTemplateUtil.java
index e850c47..df005cd 100644
--- a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/MultiBlockHtmlTemplateUtil.java
+++ b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/MultiBlockHtmlTemplateUtil.java
@@ -19,7 +19,9 @@
 package org.apache.ofbiz.widget.model;
 
 import java.io.IOException;
+import java.util.Collections;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
@@ -29,7 +31,6 @@ import java.util.Set;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpSession;
 
-import org.apache.ofbiz.base.util.Debug;
 import org.apache.ofbiz.base.util.FileUtil;
 import org.apache.ofbiz.base.util.UtilGenerics;
 import org.apache.ofbiz.base.util.UtilValidate;
@@ -63,9 +64,9 @@ public final class MultiBlockHtmlTemplateUtil {
     /**
      * For each screen containing html-template, store a set of html imports headerized from html-template.
      * The set may contain entry of an expression of the the html-template's location.
-     * In this case, we need to expand the location expression, read the html-template for any html imports.
+     * In this case, we need to expand the location expression before reading the html-template for any html imports.
      */
-    private static LinkedHashMap<String, Set<String>> htmlImportCache =
+    private static LinkedHashMap<String, Set<String>> htmlLinksForScreenCache =
             new LinkedHashMap<String, Set<String>>() {
                 private static final long serialVersionUID = 1L;
                 protected boolean removeEldestEntry(Map.Entry<String, Set<String>> eldest) {
@@ -76,7 +77,7 @@ public final class MultiBlockHtmlTemplateUtil {
      * Store set of dependent screens info, in form of screen location + hash + name, of a given parent screen.
      * The set may contain entry of an expression of the dependent screen location and will need to be expanded before use.
      */
-    private static LinkedHashMap<String, Set<String>> dependentScreenCache =
+    private static LinkedHashMap<String, Set<String>> childScreenCache =
             new LinkedHashMap<String, Set<String>>() {
                 private static final long serialVersionUID = 1L;
                 protected boolean removeEldestEntry(Map.Entry<String, Set<String>> eldest) {
@@ -87,17 +88,25 @@ public final class MultiBlockHtmlTemplateUtil {
     private MultiBlockHtmlTemplateUtil() { }
 
     /**
-     * Add child screen info to {@link MultiBlockHtmlTemplateUtil#dependentScreenCache}.
+     * Allow unit testing to check results
+     * @return
+     */
+    public static Map<String, Set<String>> getChildScreenCache() {
+        return Collections.unmodifiableMap(childScreenCache);
+    }
+
+    /**
+     * Add child screen info to {@link MultiBlockHtmlTemplateUtil#childScreenCache}.
      * @param parentModelScreen parent screen.
      * @param location screen location. Expression is allowed.
      * @param name screen name. Expression is allowed.
      */
-    public static void collectChildScreenInfo(ModelScreen parentModelScreen, String location, String name) {
+    public static void addChildScreen(ModelScreen parentModelScreen, String location, String name) {
         String key = parentModelScreen.getSourceLocation() + "#" + parentModelScreen.getName();
-        Set<String> childList = dependentScreenCache.get(key);
+        Set<String> childList = childScreenCache.get(key);
         if (childList == null) {
             childList = new LinkedHashSet<>();
-            dependentScreenCache.put(key, childList);
+            childScreenCache.put(key, childList);
         }
         if (UtilValidate.isNotEmpty(location)) {
             childList.add(location + "#" + name);
@@ -107,22 +116,22 @@ public final class MultiBlockHtmlTemplateUtil {
     }
 
     /**
-     * Add Html Imports to {@link MultiBlockHtmlTemplateUtil#htmlImportCache}.
+     * Add Html Imports to {@link MultiBlockHtmlTemplateUtil#htmlLinksForScreenCache}.
      * @param location screen location. Expression is not allowed.
      * @param name screen name. Expression is not allowed.
      * @param urls Set of html links associated with the screen. May contain expression to html-template location.
      */
-    public static void addLinksToHtmlImportCache(String location, String name, Set<String> urls) {
+    public static void addHtmlLinksToHtmlLinksForScreenCache(String location, String name, Set<String> urls) {
         if (UtilValidate.isEmpty(urls)) {
             return;
         }
         String locHashName = location + "#" + name;
-        Set<String> existingUrls = htmlImportCache.get(locHashName);
-        if (existingUrls == null) {
-            existingUrls = new LinkedHashSet<>();
-            htmlImportCache.put(locHashName, existingUrls);
+        Set<String> htmlLinks = htmlLinksForScreenCache.get(locHashName);
+        if (htmlLinks == null) {
+            htmlLinks = new LinkedHashSet<>();
+            htmlLinksForScreenCache.put(locHashName, htmlLinks);
         }
-        existingUrls.addAll(urls);
+        htmlLinks.addAll(urls);
     }
 
     /**
@@ -130,7 +139,7 @@ public final class MultiBlockHtmlTemplateUtil {
      * @param fileLocation Location to html template. Expression is not allowed.
      * @return
      */
-    public static Set<String> getHtmlImportsFromHtmlTemplate(String fileLocation) throws IOException {
+    public static Set<String> getHtmlLinksFromHtmlTemplate(String fileLocation) throws IOException {
         Set<String> imports = new LinkedHashSet<>();
         String template = FileUtil.readString("UTF-8", FileUtil.getFile(fileLocation));
         Document doc = Jsoup.parseBodyFragment(template);
@@ -150,7 +159,8 @@ public final class MultiBlockHtmlTemplateUtil {
     }
 
     /**
-     * Store the 1st screen called by request
+     * Store the 1st screen called by request.
+     * Request attribute HTML_LINKS_FOR_HEAD will be set to a string containing the initial screen location + hash + name
      * @param context
      * @param location screen location. Expression is not allowed.
      * @param name screen name. Expression is not allowed.
@@ -164,7 +174,10 @@ public final class MultiBlockHtmlTemplateUtil {
     }
 
     /**
-     * Add html links to the header
+     * Add html links to the header.
+     * Request attribute HTML_LINKS_FOR_HEAD will be set to boolean when all html links are added to the 'layoutSettings'.
+     * Request attribute HTML_LINKS_FOR_HEAD will be set to Object[] when there are expressions for screen location or
+     * template location that need to be expanded, in order to read the content of screen or template.
      * @param context
      * @throws IOException
      */
@@ -187,27 +200,32 @@ public final class MultiBlockHtmlTemplateUtil {
         }
         Object objValue = request.getAttribute(HTML_LINKS_FOR_HEAD);
         if (objValue instanceof String) {
-            Set<String> retryHtmlLinks = new LinkedHashSet<>();
+            // store expressions for Template Location that is not expanded correctly, for retry.
+            Set<String> retryTemplateLocationExpressions = new LinkedHashSet<>();
+            // store expressions for Screen Location + Hash + Name that is not expanded correctly, for retry.
+            Set<String> retryScreenLocHashNameExpressions = new LinkedHashSet<>();
             String currentLocationHashName = (String) request.getAttribute(HTML_LINKS_FOR_HEAD);
+            // store the html links that will be added to 'layoutSettings'
             Set<String> htmlLinks = new LinkedHashSet<>();
             Set<String> locHashNameList = getRelatedScreenLocationHashName(currentLocationHashName, context);
             for (String locHashName:locHashNameList) {
-                String expandLocHashName = "";
                 if (locHashName.contains("${")) {
-                    expandLocHashName = FlexibleStringExpander.expandString(locHashName, context);
-                } else {
-                    expandLocHashName = locHashName;
+                    // as getRelatedScreenLocationHashName method has already tried to expand the expression;
+                    // just add to the retry variable for the next loop.
+                    retryScreenLocHashNameExpressions.add(locHashName);
+                    continue;
                 }
-                Set<String> urls = htmlImportCache.get(expandLocHashName);
+                // check for any html links associated with the screen
+                Set<String> urls = htmlLinksForScreenCache.get(locHashName);
                 if (UtilValidate.isNotEmpty(urls)) {
                     for (String url : urls) {
+                        // if an expression is found, treat it like template location location, instead of html link
                         if (url.contains("${")) {
                             String expandUrl = FlexibleStringExpander.expandString(url, context);
                             if (UtilValidate.isNotEmpty(expandUrl)) {
-                                htmlLinks.addAll(getHtmlImportsFromHtmlTemplate(expandUrl));
+                                htmlLinks.addAll(getHtmlLinksFromHtmlTemplate(expandUrl));
                             } else {
-                                retryHtmlLinks.add(url);
-                                Debug.log("Unable to expand " + url, MODULE);
+                                retryTemplateLocationExpressions.add(url);
                             }
                         } else {
                             if (!htmlLinks.contains(url)) {
@@ -225,8 +243,68 @@ public final class MultiBlockHtmlTemplateUtil {
                     }
                 }
             }
-            if (UtilValidate.isEmpty(retryHtmlLinks)) {
+            if (UtilValidate.isEmpty(retryScreenLocHashNameExpressions) && UtilValidate.isEmpty(retryTemplateLocationExpressions)) {
                 request.setAttribute(HTML_LINKS_FOR_HEAD, true);
+            } else {
+                Object[] retry = new Object[2];
+                retry[0] = retryScreenLocHashNameExpressions;
+                retry[1] = retryTemplateLocationExpressions;
+                request.setAttribute(HTML_LINKS_FOR_HEAD, retry);
+            }
+        } else if (objValue instanceof Object[]) {
+            Object[] retry = UtilGenerics.cast(request.getAttribute(HTML_LINKS_FOR_HEAD));
+            Set<String> retryScreenLocHashNameExpressions = UtilGenerics.cast(retry[0]);
+            Set<String> retryTemplateLocationExpressions = UtilGenerics.cast(retry[1]);
+            Set<String> htmlLinks = new HashSet<>();
+            if (UtilValidate.isNotEmpty(retryScreenLocHashNameExpressions)) {
+                for (Iterator<String> it = retryScreenLocHashNameExpressions.iterator(); it.hasNext();) {
+                    String locHashName = it.next();
+                    String expandLocHashName = FlexibleStringExpander.expandString(locHashName, context);
+                    if (!expandLocHashName.startsWith("#") && !expandLocHashName.endsWith("#")) {
+                        Set<String> urls = htmlLinksForScreenCache.get(expandLocHashName);
+                        if (UtilValidate.isNotEmpty(urls)) {
+                            for (String url : urls) {
+                                if (url.contains("${")) {
+                                    retryTemplateLocationExpressions.add(url);
+                                } else {
+                                    if (!htmlLinks.contains(url)) {
+                                        htmlLinks.add(url);
+                                    }
+                                }
+                            }
+                        }
+                        it.remove();
+                    }
+                }
+            }
+
+            if (UtilValidate.isNotEmpty(retryTemplateLocationExpressions)) {
+                for (Iterator<String> it = retryTemplateLocationExpressions.iterator(); it.hasNext();) {
+                    String url = it.next();
+                    // we know url contains "${", so we expand the url
+                    String expandUrl = FlexibleStringExpander.expandString(url, context);
+                    if (UtilValidate.isNotEmpty(expandUrl)) {
+                        htmlLinks.addAll(getHtmlLinksFromHtmlTemplate(expandUrl));
+                        it.remove();
+                    }
+
+                }
+            }
+            if (UtilValidate.isNotEmpty(htmlLinks)) {
+                // check url is not already in layoutSettings.javaScripts
+                for (String url : htmlLinks) {
+                    if (!layoutSettingsJsList.contains(url)) {
+                        layoutSettingsJsList.add(url);
+                    }
+                }
+            }
+            if (UtilValidate.isEmpty(retryScreenLocHashNameExpressions) && UtilValidate.isEmpty(retryTemplateLocationExpressions)) {
+                request.setAttribute(HTML_LINKS_FOR_HEAD, true);
+            } else {
+                Object[] retry2 = new Object[2];
+                retry2[0] = retryScreenLocHashNameExpressions;
+                retry2[1] = retryTemplateLocationExpressions;
+                request.setAttribute(HTML_LINKS_FOR_HEAD, retry2);
             }
         }
 
@@ -240,12 +318,15 @@ public final class MultiBlockHtmlTemplateUtil {
     private static Set<String> getRelatedScreenLocationHashName(String locationHashName, final Map<String, Object> context) {
         Set<String> resultList = new HashSet<>();
         resultList.add(locationHashName);
-        Set<String> locHashNameList = dependentScreenCache.get(locationHashName);
+        Set<String> locHashNameList = childScreenCache.get(locationHashName);
         if (locHashNameList != null) {
             for (String locHashName : locHashNameList) {
                 String exLocHashName = "";
                 if (locHashName.contains("${")) {
                     exLocHashName = FlexibleStringExpander.expandString(locHashName, context);
+                    if (exLocHashName.startsWith("#") || exLocHashName.endsWith("#")) {
+                        exLocHashName = locHashName;
+                    }
                 } else {
                     exLocHashName = locHashName;
                 }