svn commit: r742013 - in /ofbiz/trunk/framework: base/config/ base/src/org/ofbiz/base/util/template/ entity/src/org/ofbiz/entity/ webapp/src/org/ofbiz/webapp/control/ widget/src/org/ofbiz/widget/screen/

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

svn commit: r742013 - in /ofbiz/trunk/framework: base/config/ base/src/org/ofbiz/base/util/template/ entity/src/org/ofbiz/entity/ webapp/src/org/ofbiz/webapp/control/ widget/src/org/ofbiz/widget/screen/

jonesde
Author: jonesde
Date: Sun Feb  8 07:40:40 2009
New Revision: 742013

URL: http://svn.apache.org/viewvc?rev=742013&view=rev
Log:
Implemented StringModel extension and BeansWrapper extension to load it in order to encode strings for HTML use; this is only done throught the HtmlWidget class that is part of the screen widget, so won't interfere with other FTL uses; note that there is other experimental code in here for wrapping the GenericValue object for html encoding, but that option wasn't very complete so went this way instead; this option behaves better and is more transparent than that escape x as x?html option for FTL files; there are also a few cleanups of typos and things in here; note that this means we have a different Configuration object and so a different template cache, and so we have different cache settings and such; after applying this I found a few places that encoded things they shouldn't but for the most part it is pretty good; will be committing some changes in applications in a bit to resolve a couple of these issues in ecommerce; all should watch out for funny things showing up as
 HTML text instead of being interpreted by the browser as HTML, meaning it was encoded; in extreme cases just comment out lines 71-73 of HtmlWidget.java to turn off this encoding

Added:
    ofbiz/trunk/framework/entity/src/org/ofbiz/entity/GenericValueHtmlWrapper.java   (with props)
Modified:
    ofbiz/trunk/framework/base/config/cache.properties
    ofbiz/trunk/framework/base/src/org/ofbiz/base/util/template/FreeMarkerWorker.java
    ofbiz/trunk/framework/entity/src/org/ofbiz/entity/GenericEntity.java
    ofbiz/trunk/framework/entity/src/org/ofbiz/entity/GenericValue.java
    ofbiz/trunk/framework/webapp/src/org/ofbiz/webapp/control/RequestHandler.java
    ofbiz/trunk/framework/widget/src/org/ofbiz/widget/screen/HtmlWidget.java

Modified: ofbiz/trunk/framework/base/config/cache.properties
URL: http://svn.apache.org/viewvc/ofbiz/trunk/framework/base/config/cache.properties?rev=742013&r1=742012&r2=742013&view=diff
==============================================================================
--- ofbiz/trunk/framework/base/config/cache.properties (original)
+++ ofbiz/trunk/framework/base/config/cache.properties Sun Feb  8 07:40:40 2009
@@ -91,8 +91,9 @@
 widget.tree.locationResource.expireTime=10000
 widget.tree.webappResource.expireTime=10000
 
-template.ftl.general.expireTime=10000
 template.ftl.location.expireTime=10000
+template.ftl.general.expireTime=10000
+widget.screen.template.ftl.general.expireTime=10000
 
 ModelDataFile.expireTime=10000
 

Modified: ofbiz/trunk/framework/base/src/org/ofbiz/base/util/template/FreeMarkerWorker.java
URL: http://svn.apache.org/viewvc/ofbiz/trunk/framework/base/src/org/ofbiz/base/util/template/FreeMarkerWorker.java?rev=742013&r1=742012&r2=742013&view=diff
==============================================================================
--- ofbiz/trunk/framework/base/src/org/ofbiz/base/util/template/FreeMarkerWorker.java (original)
+++ ofbiz/trunk/framework/base/src/org/ofbiz/base/util/template/FreeMarkerWorker.java Sun Feb  8 07:40:40 2009
@@ -62,7 +62,6 @@
 import freemarker.template.TemplateException;
 import freemarker.template.TemplateModel;
 import freemarker.template.TemplateModelException;
-//import com.clarkware.profiler.Profiler;
 
 /** FreeMarkerWorker - Freemarker Template Engine Utilities.
  *
@@ -73,17 +72,19 @@
     
     // use soft references for this so that things from Content records don't kill all of our memory, or maybe not for performance reasons... hmmm, leave to config file...
     public static UtilCache<String, Template> cachedTemplates = new UtilCache<String, Template>("template.ftl.general", 0, 0, false);
-    protected static Configuration defaultOfbizConfig = new Configuration();
+    protected static BeansWrapper defaultOfbizWrapper = BeansWrapper.getDefaultInstance();
+    protected static Configuration defaultOfbizConfig = makeConfiguration(defaultOfbizWrapper);
 
-    static {
-        BeansWrapper wrapper = BeansWrapper.getDefaultInstance();
-        defaultOfbizConfig.setObjectWrapper(wrapper);
-        defaultOfbizConfig.setSharedVariable("Static", wrapper.getStaticModels());
-        defaultOfbizConfig.setLocalizedLookup(false);
-        defaultOfbizConfig.setTemplateLoader(new FlexibleTemplateLoader());
+    public static Configuration makeConfiguration(BeansWrapper wrapper) {
+        Configuration newConfig = new Configuration();
+        
+        newConfig.setObjectWrapper(wrapper);
+        newConfig.setSharedVariable("Static", wrapper.getStaticModels());
+        newConfig.setLocalizedLookup(false);
+        newConfig.setTemplateLoader(new FlexibleTemplateLoader());
         try {
-            defaultOfbizConfig.setSetting("datetime_format", "yyyy-MM-dd HH:mm:ss.SSS");
-            defaultOfbizConfig.setSetting("number_format", "0.##########");
+            newConfig.setSetting("datetime_format", "yyyy-MM-dd HH:mm:ss.SSS");
+            newConfig.setSetting("number_format", "0.##########");
         } catch (TemplateException e) {
             Debug.logError("Unable to set date/time and number formats in FreeMarker: " + e, module);
         }
@@ -103,23 +104,25 @@
             if (props == null || props.isEmpty()) {
                 Debug.logError("Unable to locate properties file " + propertyURL, module);
             } else {
-                loadTransforms(loader, props);
+                loadTransforms(loader, props, newConfig);
             }
         }
+        
+        return newConfig;
     }
     
     /**
      * Protected helper method.
      */
-    protected static void loadTransforms(ClassLoader loader, Properties props) {
+    protected static void loadTransforms(ClassLoader loader, Properties props, Configuration config) {
         for (Iterator<Object> i = props.keySet().iterator(); i.hasNext();) {
-            String key = (String)i.next();
+            String key = (String) i.next();
             String className = props.getProperty(key);
             if (Debug.verboseOn()) {
                 Debug.logVerbose("Adding FTL Transform " + key + " with class " + className, module);
             }
             try {
-                defaultOfbizConfig.setSharedVariable(key, loader.loadClass(className).newInstance());
+                config.setSharedVariable(key, loader.loadClass(className).newInstance());
             } catch (Exception e) {
                 Debug.logError(e, "Could not pre-initialize dynamically loaded class: " + className + ": " + e, module);
             }
@@ -195,7 +198,7 @@
         // FIXME: the casting from Appendable to Writer is a temporary fix that could cause a
         //        run time error if in the future we will pass a different class to the method
         //        (such as a StringBuffer).
-        Environment env = template.createProcessingEnvironment(context, (Writer)outWriter);
+        Environment env = template.createProcessingEnvironment(context, (Writer) outWriter);
         applyUserSettings(env, context);
         env.process();
         return env;
@@ -261,16 +264,20 @@
      * @param templateLocation Location of the template - file path or URL
      */
     public static Template getTemplate(String templateLocation) throws TemplateException, IOException {
-        Template template = (Template) cachedTemplates.get(templateLocation);
+        return getTemplate(templateLocation, cachedTemplates, defaultOfbizConfig);
+    }
+    
+    public static Template getTemplate(String templateLocation, UtilCache<String, Template> cache, Configuration config) throws TemplateException, IOException {
+        Template template = (Template) cache.get(templateLocation);
         if (template == null) {
-            synchronized (cachedTemplates) {
-                template = (Template) cachedTemplates.get(templateLocation);
+            synchronized (cache) {
+                template = (Template) cache.get(templateLocation);
                 if (template == null) {
                     // only make the reader if we need it, and then close it right after!
                     Reader templateReader = makeReader(templateLocation);
-                    template = new Template(templateLocation, templateReader, defaultOfbizConfig);
+                    template = new Template(templateLocation, templateReader, config);
                     templateReader.close();
-                    cachedTemplates.put(templateLocation, template);
+                    cache.put(templateLocation, template);
                 }
             }
         }

Modified: ofbiz/trunk/framework/entity/src/org/ofbiz/entity/GenericEntity.java
URL: http://svn.apache.org/viewvc/ofbiz/trunk/framework/entity/src/org/ofbiz/entity/GenericEntity.java?rev=742013&r1=742012&r2=742013&view=diff
==============================================================================
--- ofbiz/trunk/framework/entity/src/org/ofbiz/entity/GenericEntity.java (original)
+++ ofbiz/trunk/framework/entity/src/org/ofbiz/entity/GenericEntity.java Sun Feb  8 07:40:40 2009
@@ -142,21 +142,21 @@
     /** Creates new GenericEntity */
     protected void init(ModelEntity modelEntity) {
         if (modelEntity == null) {
-            throw new IllegalArgumentException("Cannont create a GenericEntity with a null modelEntity parameter");
+            throw new IllegalArgumentException("Cannot create a GenericEntity with a null modelEntity parameter");
         }
         this.modelEntity = modelEntity;
         this.entityName = modelEntity.getEntityName();
         
         // check some things
         if (this.entityName == null) {
-            throw new IllegalArgumentException("Cannont create a GenericEntity with a null entityName in the modelEntity parameter");
+            throw new IllegalArgumentException("Cannot create a GenericEntity with a null entityName in the modelEntity parameter");
         }
     }
 
     /** Creates new GenericEntity from existing Map */
     protected void init(ModelEntity modelEntity, Map<String, ? extends Object> fields) {
         if (modelEntity == null) {
-            throw new IllegalArgumentException("Cannont create a GenericEntity with a null modelEntity parameter");
+            throw new IllegalArgumentException("Cannot create a GenericEntity with a null modelEntity parameter");
         }
         this.modelEntity = modelEntity;
         this.entityName = modelEntity.getEntityName();
@@ -164,14 +164,14 @@
         
         // check some things
         if (this.entityName == null) {
-            throw new IllegalArgumentException("Cannont create a GenericEntity with a null entityName in the modelEntity parameter");
+            throw new IllegalArgumentException("Cannot create a GenericEntity with a null entityName in the modelEntity parameter");
         }
     }
 
     /** Creates new GenericEntity from existing Map */
     protected void init(ModelEntity modelEntity, Object singlePkValue) {
         if (modelEntity == null) {
-            throw new IllegalArgumentException("Cannont create a GenericEntity with a null modelEntity parameter");
+            throw new IllegalArgumentException("Cannot create a GenericEntity with a null modelEntity parameter");
         }
         if (modelEntity.getPksSize() != 1) {
             throw new IllegalArgumentException("Cannot create a GenericEntity with more than one primary key field");
@@ -182,25 +182,22 @@
         
         // check some things
         if (this.entityName == null) {
-            throw new IllegalArgumentException("Cannont create a GenericEntity with a null entityName in the modelEntity parameter");
+            throw new IllegalArgumentException("Cannot create a GenericEntity with a null entityName in the modelEntity parameter");
         }
     }
 
     /** Copy Constructor: Creates new GenericEntity from existing GenericEntity */
     protected void init(GenericEntity value) {
-        if (value.modelEntity == null) {
-            throw new IllegalArgumentException("Cannont create a GenericEntity from another GenericEntity with a null modelEntity in the value parameter");
+        // check some things
+        if (value.entityName == null) {
+            throw new IllegalArgumentException("Cannot create a GenericEntity with a null entityName in the modelEntity parameter");
         }
-        this.entityName = value.modelEntity.getEntityName();
+        this.entityName = value.getEntityName();
+        // NOTE: could call getModelEntity to insure we have a value, just in case the value passed in has been serialized, but might as well leave it null to keep the object light if it isn't there
         this.modelEntity = value.modelEntity;
         if (value.fields != null) this.fields.putAll(value.fields);
         this.delegatorName = value.delegatorName;
         this.internalDelegator = value.internalDelegator;
-        
-        // check some things
-        if (this.entityName == null) {
-            throw new IllegalArgumentException("Cannont create a GenericEntity with a null entityName in the modelEntity parameter");
-        }
     }
 
     public void reset() {

Modified: ofbiz/trunk/framework/entity/src/org/ofbiz/entity/GenericValue.java
URL: http://svn.apache.org/viewvc/ofbiz/trunk/framework/entity/src/org/ofbiz/entity/GenericValue.java?rev=742013&r1=742012&r2=742013&view=diff
==============================================================================
--- ofbiz/trunk/framework/entity/src/org/ofbiz/entity/GenericValue.java (original)
+++ ofbiz/trunk/framework/entity/src/org/ofbiz/entity/GenericValue.java Sun Feb  8 07:40:40 2009
@@ -24,8 +24,8 @@
 import java.util.List;
 import java.util.Map;
 
-import javolution.lang.Reusable;
 import javolution.context.ObjectFactory;
+import javolution.lang.Reusable;
 import javolution.util.FastMap;
 
 import org.ofbiz.base.util.Debug;
@@ -33,7 +33,6 @@
 import org.ofbiz.base.util.UtilValidate;
 import org.ofbiz.entity.condition.EntityCondition;
 import org.ofbiz.entity.condition.EntityFieldMap;
-import org.ofbiz.entity.condition.EntityOperator;
 import org.ofbiz.entity.model.ModelEntity;
 import org.ofbiz.entity.model.ModelKeyMap;
 import org.ofbiz.entity.model.ModelRelation;
@@ -44,6 +43,7 @@
  * Generic Entity Value Object - Handles persistence for any defined entity.
  *
  */
+@SuppressWarnings("serial")
 public class GenericValue extends GenericEntity implements Reusable {
 
     public static final GenericValue NULL_VALUE = new NullGenericValue();

Added: ofbiz/trunk/framework/entity/src/org/ofbiz/entity/GenericValueHtmlWrapper.java
URL: http://svn.apache.org/viewvc/ofbiz/trunk/framework/entity/src/org/ofbiz/entity/GenericValueHtmlWrapper.java?rev=742013&view=auto
==============================================================================
--- ofbiz/trunk/framework/entity/src/org/ofbiz/entity/GenericValueHtmlWrapper.java (added)
+++ ofbiz/trunk/framework/entity/src/org/ofbiz/entity/GenericValueHtmlWrapper.java Sun Feb  8 07:40:40 2009
@@ -0,0 +1,105 @@
+/*******************************************************************************
+ * 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.ofbiz.entity;
+
+
+import javolution.context.ObjectFactory;
+
+import org.ofbiz.base.util.Debug;
+import org.ofbiz.base.util.StringUtil;
+
+import freemarker.ext.beans.BeansWrapper;
+import freemarker.ext.beans.MapModel;
+import freemarker.ext.beans.StringModel;
+import freemarker.template.TemplateModel;
+import freemarker.template.TemplateModelException;
+
+
+/**
+ * Generic Entity Value Object - Handles persistence for any defined entity.
+ * WARNING: This object is experimental!
+ *
+ */
+@SuppressWarnings("serial")
+public class GenericValueHtmlWrapper extends GenericValue {
+    protected static final ObjectFactory<GenericValueHtmlWrapper> genericValueHtmlWrapperFactory = new ObjectFactory<GenericValueHtmlWrapper>() {
+        protected GenericValueHtmlWrapper create() {
+            return new GenericValueHtmlWrapper();
+        }
+    };
+    
+    /** Creates new GenericValueHtmlWrapper from existing GenericValue */
+    public static GenericValueHtmlWrapper create(GenericValue value) {
+        GenericValueHtmlWrapper newValue = genericValueHtmlWrapperFactory.object();
+        try {
+            newValue.init(value);
+        } catch (RuntimeException e) {
+            Debug.logError(e, "Error in init for clone of value: " + value, module);
+            throw e;
+        }
+        return newValue;
+    }
+
+    /* NOTE: this is NOT used because there are certain FTL files that call services and things, and this messes those up, so only overriding the Map.get(Object) method to get use of this as a Map
+     * Override the basic get method, which all other get methods call so we only need to do this one (though most important for the Map.get(Object) and the getString() methods
+    public Object get(String name) {
+        Object value = super.get(name);
+        if (value instanceof String) {
+            return StringUtil.htmlEncoder.encode((String) value);
+        } else {
+            return value;
+        }
+    }*/
+
+    public Object get(Object name) {
+        Object value = super.get(name);
+        if (value instanceof String) {
+            return StringUtil.htmlEncoder.encode((String) value);
+        } else {
+            return value;
+        }
+    }
+    
+    // another experimental object, this one specifically for FTL
+    public static class GenericValueHtmlWrapperForFtl extends MapModel {
+        public GenericValueHtmlWrapperForFtl(GenericValue gv, BeansWrapper wrapper) {
+            super(gv, wrapper);
+        }
+        
+        public TemplateModel get(String key) {
+            TemplateModel tm = null;
+            try {
+                tm = super.get(key);
+            } catch (TemplateModelException e) {
+                Debug.logError(e, "Error getting Map with key [" + key + "]: " + e.toString(), module);
+            }
+            if (tm instanceof StringModel) {
+                String original = ((StringModel) tm).getAsString();
+                if (original != null) {
+                    String encoded = StringUtil.htmlEncoder.encode(original);
+                    if (!original.equals(encoded)) {
+                        return new StringModel(encoded, this.wrapper);
+                    }
+                }
+            }
+            return tm;
+        }
+    }
+}

Propchange: ofbiz/trunk/framework/entity/src/org/ofbiz/entity/GenericValueHtmlWrapper.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: ofbiz/trunk/framework/entity/src/org/ofbiz/entity/GenericValueHtmlWrapper.java
------------------------------------------------------------------------------
    svn:keywords = "Date Rev Author URL Id"

Propchange: ofbiz/trunk/framework/entity/src/org/ofbiz/entity/GenericValueHtmlWrapper.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: ofbiz/trunk/framework/webapp/src/org/ofbiz/webapp/control/RequestHandler.java
URL: http://svn.apache.org/viewvc/ofbiz/trunk/framework/webapp/src/org/ofbiz/webapp/control/RequestHandler.java?rev=742013&r1=742012&r2=742013&view=diff
==============================================================================
--- ofbiz/trunk/framework/webapp/src/org/ofbiz/webapp/control/RequestHandler.java (original)
+++ ofbiz/trunk/framework/webapp/src/org/ofbiz/webapp/control/RequestHandler.java Sun Feb  8 07:40:40 2009
@@ -950,7 +950,9 @@
     public boolean trackStats(HttpServletRequest request) {
         if (!"false".equalsIgnoreCase(context.getInitParameter("track-serverhit"))) {
             String uriString = RequestHandler.getRequestUri(request.getPathInfo());
-            return controllerConfig.requestMapMap.get(uriString).trackServerHit;
+            ConfigXMLReader.RequestMap requestMap = controllerConfig.requestMapMap.get(uriString);
+            if (requestMap == null) return false;
+            return requestMap.trackServerHit;
         } else {
             return false;
         }
@@ -959,7 +961,9 @@
     public boolean trackVisit(HttpServletRequest request) {
         if (!"false".equalsIgnoreCase(context.getInitParameter("track-visit"))) {
             String uriString = RequestHandler.getRequestUri(request.getPathInfo());
-            return controllerConfig.requestMapMap.get(uriString).trackVisit;
+            ConfigXMLReader.RequestMap requestMap = controllerConfig.requestMapMap.get(uriString);
+            if (requestMap == null) return false;
+            return requestMap.trackVisit;
         } else {
             return false;
         }

Modified: ofbiz/trunk/framework/widget/src/org/ofbiz/widget/screen/HtmlWidget.java
URL: http://svn.apache.org/viewvc/ofbiz/trunk/framework/widget/src/org/ofbiz/widget/screen/HtmlWidget.java?rev=742013&r1=742012&r2=742013&view=diff
==============================================================================
--- ofbiz/trunk/framework/widget/src/org/ofbiz/widget/screen/HtmlWidget.java (original)
+++ ofbiz/trunk/framework/widget/src/org/ofbiz/widget/screen/HtmlWidget.java Sun Feb  8 07:40:40 2009
@@ -21,7 +21,6 @@
 import java.io.IOException;
 import java.net.MalformedURLException;
 import java.util.ArrayList;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 
@@ -29,9 +28,11 @@
 
 import org.ofbiz.base.util.Debug;
 import org.ofbiz.base.util.GeneralException;
+import org.ofbiz.base.util.StringUtil;
 import org.ofbiz.base.util.UtilGenerics;
 import org.ofbiz.base.util.UtilValidate;
 import org.ofbiz.base.util.UtilXml;
+import org.ofbiz.base.util.cache.UtilCache;
 import org.ofbiz.base.util.collections.MapStack;
 import org.ofbiz.base.util.string.FlexibleStringExpander;
 import org.ofbiz.base.util.template.FreeMarkerWorker;
@@ -39,7 +40,13 @@
 import org.ofbiz.widget.html.HtmlWidgetRenderer;
 import org.w3c.dom.Element;
 
+import freemarker.ext.beans.BeansWrapper;
+import freemarker.ext.beans.StringModel;
+import freemarker.template.Configuration;
+import freemarker.template.Template;
 import freemarker.template.TemplateException;
+import freemarker.template.TemplateModel;
+import freemarker.template.TemplateModelException;
 
 /**
  * Widget Library - Screen model HTML class.
@@ -47,6 +54,37 @@
 @SuppressWarnings("serial")
 public class HtmlWidget extends ModelScreenWidget {
     public static final String module = HtmlWidget.class.getName();
+
+    public static UtilCache<String, Template> specialTemplateCache = new UtilCache<String, Template>("widget.screen.template.ftl.general", 0, 0, false);
+    protected static BeansWrapper specialBeansWrapper = new ExtendedWrapper();
+    protected static Configuration specialConfig = FreeMarkerWorker.makeConfiguration(specialBeansWrapper);
+
+    // 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 TemplateModel wrap(Object object) throws TemplateModelException {
+            /* NOTE: don't use this and the StringHtmlWrapperForFtl or things will be double-encoded
+            if (object instanceof GenericValue) {
+                return new GenericValueHtmlWrapperForFtl((GenericValue) object, this);
+            }*/
+            // This StringHtmlWrapperForFtl option seems to be the best option
+            // and handles most things without causing too many problems
+            if (object instanceof String) {
+                return new StringHtmlWrapperForFtl((String) object, this);
+            }
+            return super.wrap(object);
+        }
+    }
+
+    public static class StringHtmlWrapperForFtl extends StringModel {
+        public StringHtmlWrapperForFtl(String str, BeansWrapper wrapper) {
+            super(str, wrapper);
+        }
+        public String getAsString() {
+            return StringUtil.htmlEncoder.encode(super.getAsString());
+        }
+    }
+    
+    // End Static, begin class section
     
     protected List<ModelScreenWidget> subWidgets = new ArrayList<ModelScreenWidget>();
     
@@ -87,6 +125,64 @@
             throw new IllegalArgumentException("Template location is empty");
         }
         
+
+        /*
+        // =======================================================================
+        // Go through the context and find GenericValue objects and wrap them
+        
+        // NOTE PROBLEM: there are still problems with this as it gets some things
+        // but does not get non-entity data including lots of strings
+        // directly in the context or things prepared or derived right in
+        // the FTL file, like the results of service calls, etc; we could
+        // do something more aggressive to encode and wrap EVERYTHING in
+        // the context, but I've been thinking that even this is too much
+        // overhead and that would be crazy
+        
+        // NOTE ALTERNATIVE1: considering instead to use the FTL features to wrap
+        // everything in an <#escape x as x?html>...</#escape>, but that could
+        // cause problems with ${} expansions that have HTML in them, including:
+        // included screens (using ${screens.render(...)}), content that should
+        // have HTML in it (lots of general, product, category, etc content), etc
+        
+        // NOTE ALTERNATIVE2: kind of like the "#escape X as x?html" option,
+        // implement an FTL *Model class and load it through a ObjectWrapper
+        // FINAL NOTE: after testing all of these alternatives, this one seems
+        // to behave the best, so going with that for now.
+        
+        // isolate the scope so these wrapper objects go away after rendering is done
+        MapStack<String> contextMs;
+        if (!(context instanceof MapStack)) {
+            contextMs = MapStack.create(context);
+            context = contextMs;
+        } else {
+            contextMs = UtilGenerics.cast(context);
+        }
+
+        contextMs.push();
+        for(Map.Entry<String, Object> mapEntry: contextMs.entrySet()) {
+            Object value = mapEntry.getValue();
+            if (value instanceof GenericValue) {
+                contextMs.put(mapEntry.getKey(), GenericValueHtmlWrapper.create((GenericValue) value));
+            } else if (value instanceof List) {
+                if (((List) value).size() > 0 && ((List) value).get(0) instanceof GenericValue) {
+                    List<GenericValue> theList = (List<GenericValue>) value;
+                    List<GenericValueHtmlWrapper> newList = FastList.newInstance();
+                    for (GenericValue gv: theList) {
+                        newList.add(GenericValueHtmlWrapper.create(gv));
+                    }
+                    contextMs.put(mapEntry.getKey(), newList);
+                }
+            }
+            // TODO and NOTE: should get most stuff, but we could support Maps
+            // and Lists in Maps and such; that's tricky because we have to go
+            // through the entire Map and not just one entry, and we would
+            // have to shallow copy the whole Map too
+            
+        }
+        // this line goes at the end of the method, but moved up here to be part of the big comment about this
+        contextMs.pop();
+         */
+        
         if (location.endsWith(".ftl")) {
             try {
                 Map<String, ? extends Object> parameters = UtilGenerics.checkMap(context.get("parameters"));
@@ -94,7 +190,11 @@
                 if (insertWidgetBoundaryComments) {
                     writer.append(HtmlWidgetRenderer.formatBoundaryComment("Begin", "Template", location));
                 }
-                FreeMarkerWorker.renderTemplateAtLocation(location, context, writer);
+                
+                //FreeMarkerWorker.renderTemplateAtLocation(location, context, writer);
+                Template template = FreeMarkerWorker.getTemplate(location, specialTemplateCache, specialConfig);
+                FreeMarkerWorker.renderTemplate(template, context, writer);
+                
                 if (insertWidgetBoundaryComments) {
                     writer.append(HtmlWidgetRenderer.formatBoundaryComment("End", "Template", location));
                 }