Save This Page
Home » apache-ofbiz-09.04 » org.ofbiz.widget.screen » [javadoc | source]
    1   /*******************************************************************************
    2    * Licensed to the Apache Software Foundation (ASF) under one
    3    * or more contributor license agreements.  See the NOTICE file
    4    * distributed with this work for additional information
    5    * regarding copyright ownership.  The ASF licenses this file
    6    * to you under the Apache License, Version 2.0 (the
    7    * "License"); you may not use this file except in compliance
    8    * with the License.  You may obtain a copy of the License at
    9    *
   10    * http://www.apache.org/licenses/LICENSE-2.0
   11    *
   12    * Unless required by applicable law or agreed to in writing,
   13    * software distributed under the License is distributed on an
   14    * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
   15    * KIND, either express or implied.  See the License for the
   16    * specific language governing permissions and limitations
   17    * under the License.
   18    *******************************************************************************/
   19   package org.ofbiz.widget.screen;
   20   
   21   import java.io.IOException;
   22   import java.net.MalformedURLException;
   23   import java.util.ArrayList;
   24   import java.util.List;
   25   import java.util.Map;
   26   
   27   import javolution.util.FastMap;
   28   
   29   import org.ofbiz.base.util.Debug;
   30   import org.ofbiz.base.util.GeneralException;
   31   import org.ofbiz.base.util.StringUtil;
   32   import org.ofbiz.base.util.UtilGenerics;
   33   import org.ofbiz.base.util.UtilValidate;
   34   import org.ofbiz.base.util.UtilXml;
   35   import org.ofbiz.base.util.cache.UtilCache;
   36   import org.ofbiz.base.util.collections.MapStack;
   37   import org.ofbiz.base.util.string.FlexibleStringExpander;
   38   import org.ofbiz.base.util.template.FreeMarkerWorker;
   39   import org.ofbiz.widget.ModelWidget;
   40   import org.ofbiz.widget.html.HtmlWidgetRenderer;
   41   import org.w3c.dom.Element;
   42   
   43   import freemarker.ext.beans.BeansWrapper;
   44   import freemarker.ext.beans.StringModel;
   45   import freemarker.template.Configuration;
   46   import freemarker.template.Template;
   47   import freemarker.template.TemplateException;
   48   import freemarker.template.TemplateModel;
   49   import freemarker.template.TemplateModelException;
   50   
   51   /**
   52    * Widget Library - Screen model HTML class.
   53    */
   54   @SuppressWarnings("serial")
   55   public class HtmlWidget extends ModelScreenWidget {
   56       public static final String module = HtmlWidget.class.getName();
   57   
   58       public static UtilCache<String, Template> specialTemplateCache = new UtilCache<String, Template>("widget.screen.template.ftl.general", 0, 0, false);
   59       protected static BeansWrapper specialBeansWrapper = new ExtendedWrapper();
   60       protected static Configuration specialConfig = FreeMarkerWorker.makeConfiguration(specialBeansWrapper);
   61   
   62       // not sure if this is the best way to get FTL to use my fancy MapModel derivative, but should work at least...
   63       public static class ExtendedWrapper extends BeansWrapper {
   64           public TemplateModel wrap(Object object) throws TemplateModelException {
   65               /* NOTE: don't use this and the StringHtmlWrapperForFtl or things will be double-encoded
   66               if (object instanceof GenericValue) {
   67                   return new GenericValueHtmlWrapperForFtl((GenericValue) object, this);
   68               }*/
   69               // This StringHtmlWrapperForFtl option seems to be the best option
   70               // and handles most things without causing too many problems
   71               if (object instanceof String) {
   72                   return new StringHtmlWrapperForFtl((String) object, this);
   73               }
   74               return super.wrap(object);
   75           }
   76       }
   77   
   78       public static class StringHtmlWrapperForFtl extends StringModel {
   79           public StringHtmlWrapperForFtl(String str, BeansWrapper wrapper) {
   80               super(str, wrapper);
   81           }
   82           public String getAsString() {
   83               return StringUtil.htmlEncoder.encode(super.getAsString());
   84           }
   85       }
   86   
   87       // End Static, begin class section
   88   
   89       protected List<ModelScreenWidget> subWidgets = new ArrayList<ModelScreenWidget>();
   90   
   91       public HtmlWidget(ModelScreen modelScreen, Element htmlElement) {
   92           super(modelScreen, htmlElement);
   93           List<? extends Element> childElementList = UtilXml.childElementList(htmlElement);
   94           for (Element childElement : childElementList) {
   95               if ("html-template".equals(childElement.getNodeName())) {
   96                   this.subWidgets.add(new HtmlTemplate(modelScreen, childElement));
   97               } else if ("html-template-decorator".equals(childElement.getNodeName())) {
   98                   this.subWidgets.add(new HtmlTemplateDecorator(modelScreen, childElement));
   99               } else {
  100                   throw new IllegalArgumentException("Tag not supported under the platform-specific -> html tag with name: " + childElement.getNodeName());
  101               }
  102           }
  103       }
  104   
  105       public void renderWidgetString(Appendable writer, Map<String, Object> context, ScreenStringRenderer screenStringRenderer) throws GeneralException, IOException {
  106           for (ModelScreenWidget subWidget : subWidgets) {
  107               subWidget.renderWidgetString(writer, context, screenStringRenderer);
  108           }
  109       }
  110   
  111       public String rawString() {
  112           StringBuilder buffer = new StringBuilder("<html-widget>");
  113           for (ModelScreenWidget subWidget : subWidgets) {
  114               buffer.append(subWidget.rawString());
  115           }
  116           buffer.append("</html-widget>");
  117           return buffer.toString();
  118       }
  119   
  120       public static void renderHtmlTemplate(Appendable writer, FlexibleStringExpander locationExdr, Map<String, Object> context) {
  121           String location = locationExdr.expandString(context);
  122           //Debug.logInfo("Rendering template at location [" + location + "] with context: \n" + context, module);
  123   
  124           if (UtilValidate.isEmpty(location)) {
  125               throw new IllegalArgumentException("Template location is empty");
  126           }
  127   
  128   
  129           /*
  130           // =======================================================================
  131           // Go through the context and find GenericValue objects and wrap them
  132   
  133           // NOTE PROBLEM: there are still problems with this as it gets some things
  134           // but does not get non-entity data including lots of strings
  135           // directly in the context or things prepared or derived right in
  136           // the FTL file, like the results of service calls, etc; we could
  137           // do something more aggressive to encode and wrap EVERYTHING in
  138           // the context, but I've been thinking that even this is too much
  139           // overhead and that would be crazy
  140   
  141           // NOTE ALTERNATIVE1: considering instead to use the FTL features to wrap
  142           // everything in an <#escape x as x?html>...</#escape>, but that could
  143           // cause problems with ${} expansions that have HTML in them, including:
  144           // included screens (using ${screens.render(...)}), content that should
  145           // have HTML in it (lots of general, product, category, etc content), etc
  146   
  147           // NOTE ALTERNATIVE2: kind of like the "#escape X as x?html" option,
  148           // implement an FTL *Model class and load it through a ObjectWrapper
  149           // FINAL NOTE: after testing all of these alternatives, this one seems
  150           // to behave the best, so going with that for now.
  151   
  152           // isolate the scope so these wrapper objects go away after rendering is done
  153           MapStack<String> contextMs;
  154           if (!(context instanceof MapStack)) {
  155               contextMs = MapStack.create(context);
  156               context = contextMs;
  157           } else {
  158               contextMs = UtilGenerics.cast(context);
  159           }
  160   
  161           contextMs.push();
  162           for(Map.Entry<String, Object> mapEntry: contextMs.entrySet()) {
  163               Object value = mapEntry.getValue();
  164               if (value instanceof GenericValue) {
  165                   contextMs.put(mapEntry.getKey(), GenericValueHtmlWrapper.create((GenericValue) value));
  166               } else if (value instanceof List) {
  167                   if (((List) value).size() > 0 && ((List) value).get(0) instanceof GenericValue) {
  168                       List<GenericValue> theList = (List<GenericValue>) value;
  169                       List<GenericValueHtmlWrapper> newList = FastList.newInstance();
  170                       for (GenericValue gv: theList) {
  171                           newList.add(GenericValueHtmlWrapper.create(gv));
  172                       }
  173                       contextMs.put(mapEntry.getKey(), newList);
  174                   }
  175               }
  176               // TODO and NOTE: should get most stuff, but we could support Maps
  177               // and Lists in Maps and such; that's tricky because we have to go
  178               // through the entire Map and not just one entry, and we would
  179               // have to shallow copy the whole Map too
  180   
  181           }
  182           // this line goes at the end of the method, but moved up here to be part of the big comment about this
  183           contextMs.pop();
  184            */
  185   
  186           if (location.endsWith(".ftl")) {
  187               try {
  188                   Map<String, ? extends Object> parameters = UtilGenerics.checkMap(context.get("parameters"));
  189                   boolean insertWidgetBoundaryComments = ModelWidget.widgetBoundaryCommentsEnabled(parameters);
  190                   if (insertWidgetBoundaryComments) {
  191                       writer.append(HtmlWidgetRenderer.formatBoundaryComment("Begin", "Template", location));
  192                   }
  193   
  194                   //FreeMarkerWorker.renderTemplateAtLocation(location, context, writer);
  195                   Template template = null;
  196                   if (location.endsWith(".fo.ftl")) { // FOP can't render correctly escaped characters
  197                       template = FreeMarkerWorker.getTemplate(location);
  198                   } else {
  199                       template = FreeMarkerWorker.getTemplate(location, specialTemplateCache, specialConfig);
  200                   }
  201                   FreeMarkerWorker.renderTemplate(template, context, writer);
  202   
  203                   if (insertWidgetBoundaryComments) {
  204                       writer.append(HtmlWidgetRenderer.formatBoundaryComment("End", "Template", location));
  205                   }
  206               } catch (IllegalArgumentException e) {
  207                   String errMsg = "Error rendering included template at location [" + location + "]: " + e.toString();
  208                   Debug.logError(e, errMsg, module);
  209                   writeError(writer, errMsg);
  210               } catch (MalformedURLException e) {
  211                   String errMsg = "Error rendering included template at location [" + location + "]: " + e.toString();
  212                   Debug.logError(e, errMsg, module);
  213                   writeError(writer, errMsg);
  214               } catch (TemplateException e) {
  215                   String errMsg = "Error rendering included template at location [" + location + "]: " + e.toString();
  216                   Debug.logError(e, errMsg, module);
  217                   writeError(writer, errMsg);
  218               } catch (IOException e) {
  219                   String errMsg = "Error rendering included template at location [" + location + "]: " + e.toString();
  220                   Debug.logError(e, errMsg, module);
  221                   writeError(writer, errMsg);
  222               }
  223           } else {
  224               throw new IllegalArgumentException("Rendering not yet supported for the template at location: " + location);
  225           }
  226       }
  227   
  228       // TODO: We can make this more fancy, but for now this is very functional
  229       public static void writeError(Appendable writer, String message) {
  230           try {
  231               writer.append(message);
  232           } catch (IOException e) {
  233           }
  234       }
  235   
  236       public static class HtmlTemplate extends ModelScreenWidget {
  237           protected FlexibleStringExpander locationExdr;
  238   
  239           public HtmlTemplate(ModelScreen modelScreen, Element htmlTemplateElement) {
  240               super(modelScreen, htmlTemplateElement);
  241               this.locationExdr = FlexibleStringExpander.getInstance(htmlTemplateElement.getAttribute("location"));
  242           }
  243   
  244           public void renderWidgetString(Appendable writer, Map<String, Object> context, ScreenStringRenderer screenStringRenderer) {
  245               renderHtmlTemplate(writer, this.locationExdr, context);
  246           }
  247   
  248           public String rawString() {
  249               return "<html-template location=\"" + this.locationExdr.getOriginal() + "\"/>";
  250           }
  251       }
  252   
  253       public static class HtmlTemplateDecorator extends ModelScreenWidget {
  254           protected FlexibleStringExpander locationExdr;
  255           protected Map<String, HtmlTemplateDecoratorSection> sectionMap = FastMap.newInstance();
  256   
  257           public HtmlTemplateDecorator(ModelScreen modelScreen, Element htmlTemplateDecoratorElement) {
  258               super(modelScreen, htmlTemplateDecoratorElement);
  259               this.locationExdr = FlexibleStringExpander.getInstance(htmlTemplateDecoratorElement.getAttribute("location"));
  260   
  261               List<? extends Element> htmlTemplateDecoratorSectionElementList = UtilXml.childElementList(htmlTemplateDecoratorElement, "html-template-decorator-section");
  262               for (Element htmlTemplateDecoratorSectionElement: htmlTemplateDecoratorSectionElementList) {
  263                   String name = htmlTemplateDecoratorSectionElement.getAttribute("name");
  264                   this.sectionMap.put(name, new HtmlTemplateDecoratorSection(modelScreen, htmlTemplateDecoratorSectionElement));
  265               }
  266           }
  267   
  268           public void renderWidgetString(Appendable writer, Map<String, Object> context, ScreenStringRenderer screenStringRenderer) {
  269               // isolate the scope
  270               MapStack<String> contextMs;
  271               if (!(context instanceof MapStack)) {
  272                   contextMs = MapStack.create(context);
  273                   context = contextMs;
  274               } else {
  275                   contextMs = UtilGenerics.cast(context);
  276               }
  277   
  278               // 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
  279               MapStack<String> standAloneStack = contextMs.standAloneChildStack();
  280               standAloneStack.put("screens", new ScreenRenderer(writer, standAloneStack, screenStringRenderer));
  281               SectionsRenderer sections = new SectionsRenderer(this.sectionMap, standAloneStack, writer, screenStringRenderer);
  282   
  283               // put the sectionMap in the context, make sure it is in the sub-scope, ie after calling push on the MapStack
  284               contextMs.push();
  285               context.put("sections", sections);
  286   
  287               renderHtmlTemplate(writer, this.locationExdr, context);
  288               contextMs.pop();
  289           }
  290   
  291           public String rawString() {
  292               return "<html-template-decorator location=\"" + this.locationExdr.getOriginal() + "\"/>";
  293           }
  294       }
  295   
  296       public static class HtmlTemplateDecoratorSection extends ModelScreenWidget {
  297           protected String name;
  298           protected List<ModelScreenWidget> subWidgets;
  299   
  300           public HtmlTemplateDecoratorSection(ModelScreen modelScreen, Element htmlTemplateDecoratorSectionElement) {
  301               super(modelScreen, htmlTemplateDecoratorSectionElement);
  302               this.name = htmlTemplateDecoratorSectionElement.getAttribute("name");
  303               // read sub-widgets
  304               List<? extends Element> subElementList = UtilXml.childElementList(htmlTemplateDecoratorSectionElement);
  305               this.subWidgets = ModelScreenWidget.readSubWidgets(this.modelScreen, subElementList);
  306           }
  307   
  308           public void renderWidgetString(Appendable writer, Map<String, Object> context, ScreenStringRenderer screenStringRenderer) throws GeneralException, IOException {
  309               // render sub-widgets
  310               renderSubWidgetsString(this.subWidgets, writer, context, screenStringRenderer);
  311           }
  312   
  313           public String rawString() {
  314               return "<html-template-decorator-section name=\"" + this.name + "\"/>";
  315           }
  316       }
  317   }

Save This Page
Home » apache-ofbiz-09.04 » org.ofbiz.widget.screen » [javadoc | source]