You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ofbiz.apache.org by jl...@apache.org on 2015/01/05 09:50:36 UTC

svn commit: r1649482 [21/26] - in /ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23: ./ applications/accounting/src/org/ofbiz/accounting/invoice/ applications/accounting/src/org/ofbiz/accounting/payment/ applications/accounting/src/org/ofbiz/ac...

Modified: ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/framework/widget/src/org/ofbiz/widget/tree/ModelTree.java
URL: http://svn.apache.org/viewvc/ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/framework/widget/src/org/ofbiz/widget/tree/ModelTree.java?rev=1649482&r1=1649481&r2=1649482&view=diff
==============================================================================
--- ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/framework/widget/src/org/ofbiz/widget/tree/ModelTree.java (original)
+++ ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/framework/widget/src/org/ofbiz/widget/tree/ModelTree.java Mon Jan  5 08:50:30 2015
@@ -19,7 +19,6 @@
 package org.ofbiz.widget.tree;
 
 import java.io.IOException;
-import java.io.StringWriter;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
@@ -35,10 +34,9 @@ import javax.xml.parsers.ParserConfigura
 import org.ofbiz.base.util.Debug;
 import org.ofbiz.base.util.GeneralException;
 import org.ofbiz.base.util.StringUtil;
-import org.ofbiz.base.util.UtilFormatOut;
+import org.ofbiz.base.util.UtilCodec;
 import org.ofbiz.base.util.UtilGenerics;
 import org.ofbiz.base.util.UtilHttp;
-import org.ofbiz.base.util.UtilMisc;
 import org.ofbiz.base.util.UtilValidate;
 import org.ofbiz.base.util.UtilXml;
 import org.ofbiz.base.util.collections.MapStack;
@@ -49,11 +47,12 @@ import org.ofbiz.entity.GenericValue;
 import org.ofbiz.entity.model.ModelEntity;
 import org.ofbiz.entity.model.ModelField;
 import org.ofbiz.entity.util.EntityListIterator;
-import org.ofbiz.service.LocalDispatcher;
+import org.ofbiz.entity.util.EntityQuery;
 import org.ofbiz.widget.ModelWidget;
 import org.ofbiz.widget.ModelWidgetAction;
 import org.ofbiz.widget.ModelWidgetVisitor;
 import org.ofbiz.widget.WidgetWorker;
+import org.ofbiz.widget.WidgetWorker.Parameter;
 import org.ofbiz.widget.screen.ModelScreen;
 import org.ofbiz.widget.screen.ScreenFactory;
 import org.ofbiz.widget.screen.ScreenRenderException;
@@ -62,114 +61,119 @@ import org.w3c.dom.Element;
 import org.xml.sax.SAXException;
 
 /**
- * Widget Library - Tree model class
+ * Models the <tree> element.
+ * 
+ * @see <code>widget-tree.xsd</code>
  */
 @SuppressWarnings("serial")
 public class ModelTree extends ModelWidget {
 
-    public static final String module = ModelTree.class.getName();
+    /*
+     * ----------------------------------------------------------------------- *
+     *                     DEVELOPERS PLEASE READ
+     * ----------------------------------------------------------------------- *
+     * 
+     * This model is intended to be a read-only data structure that represents
+     * an XML element. Outside of object construction, the class should not
+     * have any behaviors.
+     * 
+     * Instances of this class will be shared by multiple threads - therefore
+     * it is immutable. DO NOT CHANGE THE OBJECT'S STATE AT RUN TIME!
+     * 
+     */
 
-    protected String defaultEntityName;
-    protected String defaultPkName;
-    protected String defaultRenderStyle;
-    protected FlexibleStringExpander defaultWrapStyleExdr;
-    protected Delegator delegator;
-    protected LocalDispatcher dispatcher;
-    protected FlexibleStringExpander expandCollapseRequestExdr;
-    protected boolean forceChildCheck;
-    protected List<ModelNode> nodeList = new ArrayList<ModelNode>();
-    protected Map<String, ModelNode> nodeMap = new HashMap<String, ModelNode>();
-    protected int openDepth;
-    protected int postTrailOpenDepth;
-    protected String rootNodeName;
-    protected FlexibleStringExpander trailNameExdr;
-    protected String treeLocation;
+    public static final String module = ModelTree.class.getName();
 
-// ===== CONSTRUCTORS =====
-    /** Default Constructor */
+    private final String defaultEntityName;
+    private final String defaultRenderStyle;
+    private final FlexibleStringExpander defaultWrapStyleExdr;
+    private final FlexibleStringExpander expandCollapseRequestExdr;
+    private final boolean forceChildCheck;
+    private final String location;
+    private final Map<String, ModelNode> nodeMap;
+    private final int openDepth;
+    private final int postTrailOpenDepth;
+    private final String rootNodeName;
+    private final FlexibleStringExpander trailNameExdr;
 
-    public ModelTree(Element treeElement, Delegator delegator, LocalDispatcher dispatcher) {
+    public ModelTree(Element treeElement, String location) {
         super(treeElement);
+        this.location = location;
         this.rootNodeName = treeElement.getAttribute("root-node-name");
-        this.defaultRenderStyle = UtilFormatOut.checkEmpty(treeElement.getAttribute("default-render-style"), "simple");
+        String defaultRenderStyle = UtilXml.checkEmpty(treeElement.getAttribute("default-render-style"), "simple");
         // A temporary hack to accommodate those who might still be using "render-style" instead of "default-render-style"
-        if (UtilValidate.isEmpty(this.defaultRenderStyle) || this.defaultRenderStyle.equals("simple")) {
+        if (defaultRenderStyle.isEmpty() || defaultRenderStyle.equals("simple")) {
             String rStyle = treeElement.getAttribute("render-style");
-            if (UtilValidate.isNotEmpty(rStyle))
-                this.defaultRenderStyle = rStyle;
+            if (!rStyle.isEmpty())
+                defaultRenderStyle = rStyle;
         }
+        this.defaultRenderStyle = defaultRenderStyle;
         this.defaultWrapStyleExdr = FlexibleStringExpander.getInstance(treeElement.getAttribute("default-wrap-style"));
         this.expandCollapseRequestExdr = FlexibleStringExpander.getInstance(treeElement.getAttribute("expand-collapse-request"));
-        this.trailNameExdr = FlexibleStringExpander.getInstance(UtilFormatOut.checkEmpty(treeElement.getAttribute("trail-name"), "trail"));
-        this.delegator = delegator;
-        this.dispatcher = dispatcher;
+        this.trailNameExdr = FlexibleStringExpander.getInstance(UtilXml.checkEmpty(treeElement.getAttribute("trail-name"),
+                "trail"));
         this.forceChildCheck = !"false".equals(treeElement.getAttribute("force-child-check"));
-        setDefaultEntityName(treeElement.getAttribute("entity-name"));
-        try {
-            openDepth = Integer.parseInt(treeElement.getAttribute("open-depth"));
-        } catch (NumberFormatException e) {
-            openDepth = 0;
-        }
-
-        try {
-            postTrailOpenDepth = Integer.parseInt(treeElement.getAttribute("post-trail-open-depth"));
-        } catch (NumberFormatException e) {
-            postTrailOpenDepth = 999;
+        this.defaultEntityName = treeElement.getAttribute("entity-name");
+        int openDepth = 0;
+        if (treeElement.hasAttribute("open-depth")) {
+            try {
+                openDepth = Integer.parseInt(treeElement.getAttribute("open-depth"));
+            } catch (NumberFormatException e) {
+                throw new IllegalArgumentException("Invalid open-depth attribute value for the tree definition with name: "
+                        + getName());
+            }
+        }
+        this.openDepth = openDepth;
+        int postTrailOpenDepth = 999;
+        if (treeElement.hasAttribute("post-trail-open-depth")) {
+            try {
+                postTrailOpenDepth = Integer.parseInt(treeElement.getAttribute("post-trail-open-depth"));
+            } catch (NumberFormatException e) {
+                throw new IllegalArgumentException(
+                        "Invalid post-trail-open-depth attribute value for the tree definition with name: " + getName());
+            }
+        }
+        this.postTrailOpenDepth = postTrailOpenDepth;
+        List<? extends Element> nodeElements = UtilXml.childElementList(treeElement, "node");
+        if (nodeElements.size() == 0) {
+            throw new IllegalArgumentException("No node elements found for the tree definition with name: " + getName());
         }
-
-        for (Element nodeElementEntry: UtilXml.childElementList(treeElement, "node")) {
+        Map<String, ModelNode> nodeMap = new HashMap<String, ModelNode>();
+        for (Element nodeElementEntry : UtilXml.childElementList(treeElement, "node")) {
             ModelNode node = new ModelNode(nodeElementEntry, this);
             String nodeName = node.getName();
-            nodeList.add(node);
-            nodeMap.put(nodeName,node);
+            nodeMap.put(nodeName, node);
         }
+        this.nodeMap = Collections.unmodifiableMap(nodeMap);
+    }
 
-        if (nodeList.size() == 0) {
-            throw new IllegalArgumentException("No node elements found for the tree definition with name: " + getName());
-        }
+    @Override
+    public void accept(ModelWidgetVisitor visitor) throws Exception {
+        visitor.visit(this);
     }
 
-    public void setDefaultEntityName(String name) {
-        String nm = name;
-        if (UtilValidate.isEmpty(nm)) {
-            nm = "Content";
-        }
-        this.defaultEntityName = nm;
-        ModelEntity modelEntity = delegator.getModelEntity(this.defaultEntityName);
-        if (modelEntity.getPksSize() == 1) {
-            ModelField modelField = modelEntity.getOnlyPk();
-            this.defaultPkName = modelField.getName();
-        }
+    @Override
+    public String getBoundaryCommentName() {
+        return location + "#" + getName();
     }
 
     public String getDefaultEntityName() {
         return this.defaultEntityName;
     }
 
-    public String getDefaultPkName() {
-        return this.defaultPkName;
-    }
-
-    public String getRootNodeName() {
-        return rootNodeName;
-    }
-
-    public String getWrapStyle(Map<String, Object> context) {
-        return this.defaultWrapStyleExdr.expandString(context);
-    }
-
-    public int getOpenDepth() {
-        return openDepth;
-    }
-
-    public int getPostTrailOpenDepth() {
-        return postTrailOpenDepth;
+    public String getDefaultPkName(Map<String, Object> context) {
+        ModelEntity modelEntity = WidgetWorker.getDelegator(context).getModelEntity(this.defaultEntityName);
+        if (modelEntity.getPksSize() == 1) {
+            ModelField modelField = modelEntity.getOnlyPk();
+            return modelField.getName();
+        }
+        return null;
     }
 
     public String getExpandCollapseRequest(Map<String, Object> context) {
         String expColReq = this.expandCollapseRequestExdr.expandString(context);
         if (UtilValidate.isEmpty(expColReq)) {
-            HttpServletRequest request = (HttpServletRequest)context.get("request");
+            HttpServletRequest request = (HttpServletRequest) context.get("request");
             String s1 = request.getRequestURI();
             int pos = s1.lastIndexOf("/");
             if (pos >= 0)
@@ -177,7 +181,6 @@ public class ModelTree extends ModelWidg
             else
                 expColReq = s1;
         }
-
         //append also the request parameters
         Map<String, Object> paramMap = UtilGenerics.checkMap(context.get("requestParameters"));
         if (UtilValidate.isNotEmpty(paramMap)) {
@@ -193,66 +196,64 @@ public class ModelTree extends ModelWidg
                 expColReq += queryString;
             }
         }
-
         return expColReq;
     }
 
-    public String getTrailName(Map<String, Object> context) {
-        return this.trailNameExdr.expandString(context);
+    public int getOpenDepth() {
+        return openDepth;
     }
 
-    @Override
-    public String getBoundaryCommentName() {
-        return treeLocation + "#" + getName();
+    public int getPostTrailOpenDepth() {
+        return postTrailOpenDepth;
+    }
+
+    public String getRenderStyle() {
+        return this.defaultRenderStyle;
+    }
+
+    public String getRootNodeName() {
+        return rootNodeName;
+    }
+
+    public String getTrailName(Map<String, Object> context) {
+        return this.trailNameExdr.expandString(context);
     }
 
-    public void setTreeLocation(String treeLocation) {
-        this.treeLocation = treeLocation;
+    public String getWrapStyle(Map<String, Object> context) {
+        return this.defaultWrapStyleExdr.expandString(context);
     }
 
     /**
-     * Renders this tree to a String, i.e. in a text format, as defined with the
-     * TreeStringRenderer implementation.
+     * Renders this model.
      *
-     * @param buf the StringBuffer Object
-     * @param context Map containing the tree context; the following are
-     *   reserved words in this context: parameters (Map), isError (Boolean),
-     *   itemIndex (Integer, for lists only, otherwise null), bshInterpreter,
-     *   treeName (String, optional alternate name for tree, defaults to the
-     *   value of the name attribute)
-     * @param treeStringRenderer An implementation of the TreeStringRenderer
-     *   interface that is responsible for the actual text generation for
-     *   different tree elements; implementing your own makes it possible to
-     *   use the same tree definitions for many types of tree UIs
+     * @param writer
+     * @param context
+     * @param treeStringRenderer
      */
     @SuppressWarnings("rawtypes")
-    public void renderTreeString(StringBuffer buf, Map<String, Object> context, TreeStringRenderer treeStringRenderer) throws GeneralException {
+    public void renderTreeString(Appendable writer, Map<String, Object> context, TreeStringRenderer treeStringRenderer)
+            throws GeneralException {
         Map<String, Object> parameters = UtilGenerics.checkMap(context.get("parameters"));
-
         ModelNode node = nodeMap.get(rootNodeName);
-
         String trailName = trailNameExdr.expandString(context);
-        String treeString = (String)context.get(trailName);
+        String treeString = (String) context.get(trailName);
         if (UtilValidate.isEmpty(treeString)) {
-            treeString = (String)parameters.get(trailName);
+            treeString = (String) parameters.get(trailName);
         }
         List<String> trail = null;
         if (UtilValidate.isNotEmpty(treeString)) {
             trail = StringUtil.split(treeString, "|");
             if (UtilValidate.isEmpty(trail))
                 throw new RuntimeException("Tree 'trail' value is empty.");
-
             context.put("rootEntityId", trail.get(0));
-            context.put(defaultPkName, trail.get(0));
+            context.put(getDefaultPkName(context), trail.get(0));
         } else {
             trail = new LinkedList<String>();
         }
         context.put("targetNodeTrail", trail);
         context.put("currentNodeTrail", new LinkedList());
-        StringWriter writer = new StringWriter();
         try {
             node.renderNodeString(writer, context, treeStringRenderer, 0);
-            buf.append(writer.toString());
         } catch (IOException e2) {
             String errMsg = "Error rendering included label with name [" + getName() + "] : " + e2.toString();
             Debug.logError(e2, errMsg, module);
@@ -260,38 +261,28 @@ public class ModelTree extends ModelWidg
         }
     }
 
-    public LocalDispatcher getDispatcher() {
-        return this.dispatcher;
-    }
-
-    public Delegator getDelegator() {
-        return this.delegator;
-    }
-
-    public String getRenderStyle() {
-        return this.defaultRenderStyle;
-    }
-
-
+    /**
+     * Models the &lt;node&gt; element.
+     * 
+     * @see <code>widget-tree.xsd</code>
+     */
     public static class ModelNode extends ModelWidget {
 
         private final List<ModelWidgetAction> actions;
-        protected ModelTreeCondition condition;
-        protected String entityName;
-        protected String entryName;
-        protected String expandCollapseStyle;
-        protected Image image;
-        protected Label label;
-        protected Link link;
-        protected ModelTree modelTree;
-        protected String pkName;
-        protected String renderStyle;
-        protected FlexibleStringExpander screenLocationExdr;
-        protected FlexibleStringExpander screenNameExdr;
-        protected String shareScope;
-        protected List<ModelSubNode> subNodeList = new ArrayList<ModelSubNode>();
-        protected List<Object []> subNodeValues;
-        protected FlexibleStringExpander wrapStyleExdr;
+        private final ModelTreeCondition condition;
+        private final String entityName;
+        private final String entryName;
+        private final String expandCollapseStyle;
+        private final Label label;
+        private final Link link;
+        private final ModelTree modelTree;
+        private final String pkName;
+        private final String renderStyle;
+        private final FlexibleStringExpander screenLocationExdr;
+        private final FlexibleStringExpander screenNameExdr;
+        private final String shareScope;
+        private final List<ModelSubNode> subNodeList;
+        private final FlexibleStringExpander wrapStyleExdr;
 
         public ModelNode(Element nodeElement, ModelTree modelTree) {
             super(nodeElement);
@@ -299,18 +290,18 @@ public class ModelTree extends ModelWidg
             this.expandCollapseStyle = nodeElement.getAttribute("expand-collapse-style");
             this.wrapStyleExdr = FlexibleStringExpander.getInstance(nodeElement.getAttribute("wrap-style"));
             this.renderStyle = nodeElement.getAttribute("render-style");
-            this.entryName = UtilFormatOut.checkEmpty(nodeElement.getAttribute("entry-name"), null);
-            setEntityName(nodeElement.getAttribute("entity-name"));
-            if (this.pkName == null || nodeElement.hasAttribute("join-field-name"))
-                this.pkName = nodeElement.getAttribute("join-field-name");
+            this.entryName = nodeElement.getAttribute("entry-name");
+            this.entityName = nodeElement.getAttribute("entity-name");
+            this.pkName = nodeElement.getAttribute("join-field-name");
             ArrayList<ModelWidgetAction> actions = new ArrayList<ModelWidgetAction>();
+            // FIXME: Validate child elements, should be only one of actions, entity-one, service, script.
             Element actionsElement = UtilXml.firstChildElement(nodeElement, "actions");
             if (actionsElement != null) {
                 actions.addAll(ModelTreeAction.readNodeActions(this, actionsElement));
             }
             Element actionElement = UtilXml.firstChildElement(nodeElement, "entity-one");
             if (actionElement != null) {
-               actions.add(new ModelWidgetAction.EntityOne(this, actionElement));
+                actions.add(new ModelWidgetAction.EntityOne(this, actionElement));
             }
             actionElement = UtilXml.firstChildElement(nodeElement, "service");
             if (actionElement != null) {
@@ -322,156 +313,135 @@ public class ModelTree extends ModelWidg
             }
             actions.trimToSize();
             this.actions = Collections.unmodifiableList(actions);
-
             Element screenElement = UtilXml.firstChildElement(nodeElement, "include-screen");
             if (screenElement != null) {
-                this.screenNameExdr =  FlexibleStringExpander.getInstance(screenElement.getAttribute("name"));
-                this.screenLocationExdr =  FlexibleStringExpander.getInstance(screenElement.getAttribute("location"));
-                this.shareScope =  screenElement.getAttribute("share-scope");
+                this.screenNameExdr = FlexibleStringExpander.getInstance(screenElement.getAttribute("name"));
+                this.screenLocationExdr = FlexibleStringExpander.getInstance(screenElement.getAttribute("location"));
+                this.shareScope = screenElement.getAttribute("share-scope");
+            } else {
+                this.screenNameExdr = FlexibleStringExpander.getInstance("");
+                this.screenLocationExdr = FlexibleStringExpander.getInstance("");
+                this.shareScope = "";
             }
-
             Element labelElement = UtilXml.firstChildElement(nodeElement, "label");
             if (labelElement != null) {
                 this.label = new Label(labelElement);
+            } else {
+                this.label = null;
             }
-
             Element linkElement = UtilXml.firstChildElement(nodeElement, "link");
             if (linkElement != null) {
                 this.link = new Link(linkElement);
+            } else {
+                this.link = null;
             }
-
-            Element imageElement = UtilXml.firstChildElement(nodeElement, "image");
-            if (imageElement != null) {
-                this.image = new Image(imageElement);
-            }
-
-            /* there are situations in which nothing should be displayed
-            if (screenElement == null && labelElement == null && linkElement == null) {
-                throw new IllegalArgumentException("Neither 'screen' nor 'label' nor 'link' found for the node definition with name: " + this.name);
-            }
-            */
             Element conditionElement = UtilXml.firstChildElement(nodeElement, "condition");
             if (conditionElement != null) {
                 this.condition = new ModelTreeCondition(modelTree, conditionElement);
+            } else {
+                this.condition = null;
             }
-
-            for (Element subNodeElementEntry: UtilXml.childElementList(nodeElement, "sub-node")) {
-                ModelSubNode subNode = new ModelSubNode(subNodeElementEntry, this);
-                subNodeList.add(subNode);
+            List<? extends Element> nodeElements = UtilXml.childElementList(nodeElement, "sub-node");
+            if (!nodeElements.isEmpty()) {
+                List<ModelSubNode> subNodeList = new ArrayList<ModelSubNode>();
+                for (Element subNodeElementEntry : nodeElements) {
+                    ModelSubNode subNode = new ModelSubNode(subNodeElementEntry, this);
+                    subNodeList.add(subNode);
+                }
+                this.subNodeList = Collections.unmodifiableList(subNodeList);
+            } else {
+                this.subNodeList = Collections.emptyList();
             }
+        }
 
-
+        @Override
+        public void accept(ModelWidgetVisitor visitor) throws Exception {
+            visitor.visit(this);
         }
 
-        public void renderNodeString(Appendable writer, Map<String, Object> context,
-                TreeStringRenderer treeStringRenderer, int depth)
-                throws IOException, GeneralException {
-            boolean passed = true;
-            if (this.condition != null) {
-                if (!this.condition.eval(context)) {
-                    passed = false;
-                }
-            }
-            //Debug.logInfo("in ModelMenu, name:" + this.getName(), module);
-            if (passed) {
-                List<String> currentNodeTrail = UtilGenerics.toList(context.get("currentNodeTrail"));
-                context.put("processChildren", Boolean.TRUE);
-                // this action will usually obtain the "current" entity
-                ModelTreeAction.runSubActions(this.actions, context);
-                String pkName = getPkName();
-                String id = null;
-                if (UtilValidate.isNotEmpty(this.entryName)) {
-                    id = UtilGenerics.<Map<String, String>>cast(context.get(this.entryName)).get(pkName);
-                } else {
-                    id = (String) context.get(pkName);
-                }
-                currentNodeTrail.add(id);
-                treeStringRenderer.renderNodeBegin(writer, context, this, depth);
-                //if (Debug.infoOn()) Debug.logInfo(" context:" +
-                // context.entrySet(), module);
-                try {
-                    String screenName = null;
-                    if (screenNameExdr != null)
-                        screenName = screenNameExdr.expandString(context);
-                    String screenLocation = null;
-                    if (screenLocationExdr != null)
-                        screenLocation = screenLocationExdr.expandString(context);
-                    if (screenName != null && screenLocation != null) {
-                        ScreenStringRenderer screenStringRenderer = treeStringRenderer.getScreenStringRenderer(context);
-                        ModelScreen modelScreen = ScreenFactory.getScreenFromLocation(screenLocation, screenName);
-                        modelScreen.renderScreenString(writer, context, screenStringRenderer);
-                    }
-                    if (label != null) {
-                        label.renderLabelString(writer, context, treeStringRenderer);
+        private List<Object[]> getChildren(Map<String, Object> context) {
+            List<Object[]> subNodeValues = new ArrayList<Object[]>();
+            for (ModelSubNode subNode : subNodeList) {
+                String nodeName = subNode.getNodeName(context);
+                ModelNode node = modelTree.nodeMap.get(nodeName);
+                List<ModelWidgetAction> subNodeActions = subNode.getActions();
+                //if (Debug.infoOn()) Debug.logInfo(" context.currentValue:" + context.get("currentValue"), module);
+                ModelWidgetAction.runSubActions(subNodeActions, context);
+                // List dataFound = (List)context.get("dataFound");
+                Iterator<? extends Map<String, ? extends Object>> dataIter = subNode.getListIterator(context);
+                if (dataIter instanceof EntityListIterator) {
+                    EntityListIterator eli = (EntityListIterator) dataIter;
+                    Map<String, Object> val = null;
+                    while ((val = eli.next()) != null) {
+                        Object[] arr = { node, val };
+                        subNodeValues.add(arr);
                     }
-                    if (link != null) {
-                        link.renderLinkString(writer, context, treeStringRenderer);
+                    try {
+                        eli.close();
+                    } catch (GenericEntityException e) {
+                        Debug.logError(e, module);
+                        throw new RuntimeException(e.getMessage());
                     }
-                    treeStringRenderer.renderLastElement(writer, context, this);
-                    Boolean processChildren = (Boolean) context.get("processChildren");
-                    //if (Debug.infoOn()) Debug.logInfo(" processChildren:" + processChildren, module);
-                    if (processChildren.booleanValue()) {
-                        getChildren(context);
-                        int newDepth = depth + 1;
-                        for (Object[] arr: this.subNodeValues) {
-                            ModelNode node = (ModelNode) arr[0];
-                            Map<String, Object> val = UtilGenerics.checkMap(arr[1]);
-                            //GenericPK pk = val.getPrimaryKey();
-                            //if (Debug.infoOn()) Debug.logInfo(" pk:" + pk,
-                            // module);
-                            String thisPkName = node.getPkName();
-                            String thisEntityId = (String) val.get(thisPkName);
-                            MapStack<String> newContext = MapStack.create(context);
-                            newContext.push();
-                            String nodeEntryName = node.getEntryName();
-                            if (UtilValidate.isNotEmpty(nodeEntryName)) {
-                                newContext.put(nodeEntryName, val);
-                            } else {
-                                newContext.putAll(val);
-                            }
-                            String targetEntityId = null;
-                            List<String> targetNodeTrail = UtilGenerics.checkList(context.get("targetNodeTrail"));
-                            if (newDepth < targetNodeTrail.size()) {
-                                targetEntityId = targetNodeTrail.get(newDepth);
-                            }
-                            if ((targetEntityId != null && targetEntityId.equals(thisEntityId)) || this.showPeers(newDepth, context)) {
-                                node.renderNodeString(writer, newContext, treeStringRenderer, newDepth);
-                            }
-                        }
+                } else if (dataIter != null) {
+                    while (dataIter.hasNext()) {
+                        Map<String, ? extends Object> val = dataIter.next();
+                        Object[] arr = { node, val };
+                        subNodeValues.add(arr);
                     }
-                } catch (ScreenRenderException e) {
-                    String errMsg = "Error rendering included label with name ["
-                            + getName() + "] : " + e.toString();
-                    Debug.logError(e, errMsg, module);
-                    throw new RuntimeException(errMsg);
-                } catch (SAXException e) {
-                    String errMsg = "Error rendering included label with name ["
-                            + getName() + "] : " + e.toString();
-                    Debug.logError(e, errMsg, module);
-                    throw new RuntimeException(errMsg);
-                } catch (ParserConfigurationException e3) {
-                    String errMsg = "Error rendering included label with name ["
-                            + getName() + "] : " + e3.toString();
-                    Debug.logError(e3, errMsg, module);
-                    throw new RuntimeException(errMsg);
-                } catch (IOException e2) {
-                    String errMsg = "Error rendering included label with name ["
-                            + getName() + "] : " + e2.toString();
-                    Debug.logError(e2, errMsg, module);
-                    throw new RuntimeException(errMsg);
                 }
-                treeStringRenderer.renderNodeEnd(writer, context, this);
-                int removeIdx = currentNodeTrail.size() - 1;
-                if (removeIdx >= 0) currentNodeTrail.remove(removeIdx);
             }
+            return subNodeValues;
+        }
+
+        public String getEntityName() {
+            if (!this.entityName.isEmpty()) {
+                return this.entityName;
+            } else {
+                return this.modelTree.getDefaultEntityName();
+            }
+        }
+
+        public String getEntryName() {
+            return this.entryName;
+        }
+
+        public String getExpandCollapseStyle() {
+            return expandCollapseStyle;
+        }
+
+        public ModelTree getModelTree() {
+            return this.modelTree;
+        }
+
+        public String getPkName(Map<String, Object> context) {
+            if (UtilValidate.isNotEmpty(this.pkName)) {
+                return this.pkName;
+            } else {
+                return this.modelTree.getDefaultPkName(context);
+            }
+        }
+
+        public String getRenderStyle() {
+            if (this.renderStyle.isEmpty())
+                return modelTree.getRenderStyle();
+            return this.renderStyle;
+        }
+
+        public String getWrapStyle(Map<String, Object> context) {
+            String val = this.wrapStyleExdr.expandString(context);
+            if (val.isEmpty()) {
+                val = this.modelTree.getWrapStyle(context);
+            }
+            return val;
         }
 
         public boolean hasChildren(Map<String, Object> context) {
+            List<Object[]> subNodeValues = getChildren(context);
             boolean hasChildren = false;
             Long nodeCount = null;
             String countFieldName = "childBranchCount";
             Object obj = null;
-            if (UtilValidate.isNotEmpty(this.entryName)) {
+            if (!this.entryName.isEmpty()) {
                 Map<String, Object> map = UtilGenerics.cast(context.get(this.entryName));
                 if (map instanceof GenericValue) {
                     ModelEntity modelEntity = ((GenericValue) map).getModelEntity();
@@ -486,7 +456,7 @@ public class ModelTree extends ModelWidg
                 nodeCount = (Long) obj;
             }
             String entName = this.getEntityName();
-            Delegator delegator = modelTree.getDelegator();
+            Delegator delegator = WidgetWorker.getDelegator(context);
             ModelEntity modelEntity = delegator.getModelEntity(entName);
             ModelField modelField = null;
             if (modelEntity.isField(countFieldName)) {
@@ -509,17 +479,17 @@ public class ModelTree extends ModelWidg
                     }
                 }
                 */
-                nodeCount = Long.valueOf(this.subNodeValues.size());
-                String pkName = this.getPkName();
+                nodeCount = Long.valueOf(subNodeValues.size());
+                String pkName = this.getPkName(context);
                 String id = null;
-                if (UtilValidate.isNotEmpty(this.entryName)) {
-                    id = UtilGenerics.<Map<String,String>>cast(context.get(this.entryName)).get(pkName);
+                if (!this.entryName.isEmpty()) {
+                    id = UtilGenerics.<Map<String, String>> cast(context.get(this.entryName)).get(pkName);
                 } else {
                     id = (String) context.get(pkName);
                 }
                 try {
                     if (id != null && modelEntity.getPksSize() == 1) {
-                        GenericValue entity = delegator.findOne(entName, UtilMisc.toMap(pkName, id), false);
+                        GenericValue entity = EntityQuery.use(delegator).from(entName).where(pkName, id).queryOne();
                         if (modelEntity.isField("childBranchCount")) {
                             entity.put("childBranchCount", nodeCount);
                             entity.store();
@@ -535,64 +505,17 @@ public class ModelTree extends ModelWidg
                     nodeCount = Long.valueOf(subNodeValues.size());
                 }
             }
-
             if (nodeCount != null && nodeCount.intValue() > 0) {
                 hasChildren = true;
             }
-
             return hasChildren;
         }
 
-        public void getChildren(Map<String, Object> context) {
-             this.subNodeValues = new ArrayList<Object []>();
-             for (ModelSubNode subNode: subNodeList) {
-                 String nodeName = subNode.getNodeName(context);
-                 ModelNode node = modelTree.nodeMap.get(nodeName);
-                 List<ModelWidgetAction> subNodeActions = subNode.getActions();
-                 //if (Debug.infoOn()) Debug.logInfo(" context.currentValue:" + context.get("currentValue"), module);
-                 ModelWidgetAction.runSubActions(subNodeActions, context);
-                 // List dataFound = (List)context.get("dataFound");
-                 Iterator<? extends Map<String, ? extends Object>> dataIter =  subNode.getListIterator();
-                 if (dataIter instanceof EntityListIterator) {
-                     EntityListIterator eli = (EntityListIterator) dataIter;
-                     Map<String, Object> val = null;
-                     while ((val = eli.next()) != null) {
-                         Object [] arr = {node, val};
-                         this.subNodeValues.add(arr);
-                     }
-                     try {
-                         eli.close();
-                     } catch (GenericEntityException e) {
-                         Debug.logError(e, module);
-                         throw new RuntimeException(e.getMessage());
-                     }
-                 } else if (dataIter != null) {
-                     while (dataIter.hasNext()) {
-                         Map<String, ? extends Object> val = dataIter.next();
-                         Object [] arr = {node, val};
-                         this.subNodeValues.add(arr);
-                     }
-                 }
-             }
-        }
-
-        public String getEntryName() {
-            return this.entryName;
-        }
-
-        public String getRenderStyle() {
-            String rStyle = this.renderStyle;
-            if (UtilValidate.isEmpty(rStyle))
-                rStyle = modelTree.getRenderStyle();
-            return rStyle;
-        }
-
         public boolean isExpandCollapse() {
             boolean isExpCollapse = false;
             String rStyle = getRenderStyle();
             if (rStyle != null && rStyle.equals("expand-collapse"))
                 isExpCollapse = true;
-
             return isExpCollapse;
         }
 
@@ -602,7 +525,6 @@ public class ModelTree extends ModelWidg
             if (rStyle != null && (rStyle.equals("follow-trail") || rStyle.equals("show-peers") || rStyle.equals("follow-trail"))) {
                 isFollowTrail = true;
             }
-
             return isFollowTrail;
         }
 
@@ -610,182 +532,214 @@ public class ModelTree extends ModelWidg
             return getName().equals(modelTree.getRootNodeName());
         }
 
-        public boolean showPeers(int currentDepth, Map<String, Object> context) {
-            int trailSize = 0;
-            List<?> trail = UtilGenerics.checkList(context.get("targetNodeTrail"));
-            int openDepth = modelTree.getOpenDepth();
-            int postTrailOpenDepth = modelTree.getPostTrailOpenDepth();
-            if (trail != null) trailSize = trail.size();
-
-            boolean showPeers = false;
-            String rStyle = getRenderStyle();
-            if (rStyle == null) {
-                showPeers = true;
-            } else if (!isFollowTrail()) {
-                showPeers = true;
-            } else if ((currentDepth < trailSize) && (rStyle != null) &&  (rStyle.equals("show-peers") || rStyle.equals("expand-collapse"))) {
-                showPeers = true;
-            } else if (openDepth >= currentDepth) {
-                showPeers = true;
-            } else {
-                int depthAfterTrail = currentDepth - trailSize;
-                if (depthAfterTrail >= 0 && depthAfterTrail <= postTrailOpenDepth) showPeers = true;
-            }
-
-            return showPeers;
-        }
-
-        public String getExpandCollapseStyle() {
-            return expandCollapseStyle;
-        }
-
-        public String getWrapStyle(Map<String, Object> context) {
-            String val = this.wrapStyleExdr.expandString(context);
-            if (UtilValidate.isEmpty(val)) {
-                val = this.modelTree.getWrapStyle(context);
+        public void renderNodeString(Appendable writer, Map<String, Object> context, TreeStringRenderer treeStringRenderer,
+                int depth) throws IOException, GeneralException {
+            boolean passed = true;
+            if (this.condition != null) {
+                if (!this.condition.eval(context)) {
+                    passed = false;
+                }
             }
-            return val;
-        }
-
-        public ModelTree getModelTree() {
-            return this.modelTree;
-        }
-
-        public void setEntityName(String name) {
-            this.entityName = name;
-            if (UtilValidate.isNotEmpty(this.entityName)) {
-                ModelEntity modelEntity = modelTree.delegator.getModelEntity(this.entityName);
-                if (modelEntity.getPksSize() == 1) {
-                    ModelField modelField = modelEntity.getOnlyPk();
-                    this.pkName = modelField.getName();
+            //Debug.logInfo("in ModelMenu, name:" + this.getName(), module);
+            if (passed) {
+                List<String> currentNodeTrail = UtilGenerics.toList(context.get("currentNodeTrail"));
+                context.put("processChildren", Boolean.TRUE);
+                // this action will usually obtain the "current" entity
+                ModelTreeAction.runSubActions(this.actions, context);
+                String pkName = getPkName(context);
+                String id = null;
+                if (!this.entryName.isEmpty()) {
+                    id = UtilGenerics.<Map<String, String>> cast(context.get(this.entryName)).get(pkName);
                 } else {
-                    List<String> pkFieldsName = modelEntity.getPkFieldNames();
-                    StringBuilder sb = new StringBuilder();
-                    for (String pk: pkFieldsName) {
-                            sb.append(pk);
-                            sb.append("|");
+                    id = (String) context.get(pkName);
+                }
+                currentNodeTrail.add(id);
+                treeStringRenderer.renderNodeBegin(writer, context, this, depth);
+                //if (Debug.infoOn()) Debug.logInfo(" context:" +
+                // context.entrySet(), module);
+                try {
+                    String screenName = null;
+                    if (!screenNameExdr.isEmpty())
+                        screenName = screenNameExdr.expandString(context);
+                    String screenLocation = null;
+                    if (!screenLocationExdr.isEmpty())
+                        screenLocation = screenLocationExdr.expandString(context);
+                    if (screenName != null && screenLocation != null) {
+                        ScreenStringRenderer screenStringRenderer = treeStringRenderer.getScreenStringRenderer(context);
+                        ModelScreen modelScreen = ScreenFactory.getScreenFromLocation(screenLocation, screenName);
+                        modelScreen.renderScreenString(writer, context, screenStringRenderer);
+                    }
+                    if (label != null) {
+                        label.renderLabelString(writer, context, treeStringRenderer);
+                    }
+                    if (link != null) {
+                        link.renderLinkString(writer, context, treeStringRenderer);
+                    }
+                    treeStringRenderer.renderLastElement(writer, context, this);
+                    Boolean processChildren = (Boolean) context.get("processChildren");
+                    //if (Debug.infoOn()) Debug.logInfo(" processChildren:" + processChildren, module);
+                    if (processChildren.booleanValue()) {
+                        List<Object[]> subNodeValues = getChildren(context);
+                        int newDepth = depth + 1;
+                        for (Object[] arr : subNodeValues) {
+                            ModelNode node = (ModelNode) arr[0];
+                            Map<String, Object> val = UtilGenerics.checkMap(arr[1]);
+                            //GenericPK pk = val.getPrimaryKey();
+                            //if (Debug.infoOn()) Debug.logInfo(" pk:" + pk,
+                            // module);
+                            String thisPkName = node.getPkName(context);
+                            String thisEntityId = (String) val.get(thisPkName);
+                            MapStack<String> newContext = MapStack.create(context);
+                            newContext.push();
+                            String nodeEntryName = node.getEntryName();
+                            if (!nodeEntryName.isEmpty()) {
+                                newContext.put(nodeEntryName, val);
+                            } else {
+                                newContext.putAll(val);
+                            }
+                            String targetEntityId = null;
+                            List<String> targetNodeTrail = UtilGenerics.checkList(context.get("targetNodeTrail"));
+                            if (newDepth < targetNodeTrail.size()) {
+                                targetEntityId = targetNodeTrail.get(newDepth);
+                            }
+                            if ((targetEntityId != null && targetEntityId.equals(thisEntityId))
+                                    || this.showPeers(newDepth, context)) {
+                                node.renderNodeString(writer, newContext, treeStringRenderer, newDepth);
+                            }
+                        }
                     }
-                    this.pkName = sb.toString();
+                } catch (ScreenRenderException e) {
+                    String errMsg = "Error rendering included label with name [" + getName() + "] : " + e.toString();
+                    Debug.logError(e, errMsg, module);
+                    throw new RuntimeException(errMsg);
+                } catch (SAXException e) {
+                    String errMsg = "Error rendering included label with name [" + getName() + "] : " + e.toString();
+                    Debug.logError(e, errMsg, module);
+                    throw new RuntimeException(errMsg);
+                } catch (ParserConfigurationException e3) {
+                    String errMsg = "Error rendering included label with name [" + getName() + "] : " + e3.toString();
+                    Debug.logError(e3, errMsg, module);
+                    throw new RuntimeException(errMsg);
+                } catch (IOException e2) {
+                    String errMsg = "Error rendering included label with name [" + getName() + "] : " + e2.toString();
+                    Debug.logError(e2, errMsg, module);
+                    throw new RuntimeException(errMsg);
                 }
+                treeStringRenderer.renderNodeEnd(writer, context, this);
+                int removeIdx = currentNodeTrail.size() - 1;
+                if (removeIdx >= 0)
+                    currentNodeTrail.remove(removeIdx);
             }
         }
 
-        public String getEntityName() {
-            if (UtilValidate.isNotEmpty(this.entityName)) {
-                return this.entityName;
-            } else {
-                return this.modelTree.getDefaultEntityName();
-            }
-        }
+        public boolean showPeers(int currentDepth, Map<String, Object> context) {
+            int trailSize = 0;
+            List<?> trail = UtilGenerics.checkList(context.get("targetNodeTrail"));
+            int openDepth = modelTree.getOpenDepth();
+            int postTrailOpenDepth = modelTree.getPostTrailOpenDepth();
+            if (trail != null)
+                trailSize = trail.size();
 
-        public String getPkName() {
-            if (UtilValidate.isNotEmpty(this.pkName)) {
-                return this.pkName;
+            boolean showPeers = false;
+            String rStyle = getRenderStyle();
+            if (rStyle == null) {
+                showPeers = true;
+            } else if (!isFollowTrail()) {
+                showPeers = true;
+            } else if ((currentDepth < trailSize) && (rStyle != null)
+                    && (rStyle.equals("show-peers") || rStyle.equals("expand-collapse"))) {
+                showPeers = true;
+            } else if (openDepth >= currentDepth) {
+                showPeers = true;
             } else {
-                return this.modelTree.getDefaultPkName();
+                int depthAfterTrail = currentDepth - trailSize;
+                if (depthAfterTrail >= 0 && depthAfterTrail <= postTrailOpenDepth)
+                    showPeers = true;
             }
+            return showPeers;
         }
 
-        public void setPkName(String pkName) {
-            this.pkName = pkName;
-        }
-
-        public static class ModelSubNode extends ModelWidget {
+        /**
+         * Models the &lt;image&gt; element.
+         * 
+         * @see <code>widget-tree.xsd</code>
+         */
+        public static class Image {
 
-            private final List<ModelWidgetAction> actions;
-            protected ModelNode rootNode;
-            protected FlexibleStringExpander nodeNameExdr;
-            protected ListIterator<? extends Map<String, ? extends Object>> listIterator;
+            private final FlexibleStringExpander borderExdr;
+            private final FlexibleStringExpander heightExdr;
+            private final FlexibleStringExpander idExdr;
+            private final FlexibleStringExpander srcExdr;
+            private final FlexibleStringExpander styleExdr;
+            private final String urlMode;
+            private final FlexibleStringExpander widthExdr;
 
-            public ModelSubNode(Element subNodeElement, ModelNode modelNode) {
-                super(subNodeElement);
-                this.rootNode = modelNode;
-                this.nodeNameExdr = FlexibleStringExpander.getInstance(subNodeElement.getAttribute("node-name"));
-                ArrayList<ModelWidgetAction> actions = new ArrayList<ModelWidgetAction>();
-                Element actionsElement = UtilXml.firstChildElement(subNodeElement, "actions");
-                if (actionsElement != null) {
-                    actions.addAll(ModelTreeAction.readNodeActions(this, actionsElement));
-                }
-                Element actionElement = UtilXml.firstChildElement(subNodeElement, "entity-and");
-                if (actionElement != null) {
-                   actions.add(new ModelTreeAction.EntityAnd(this, actionElement));
-                }
-                actionElement = UtilXml.firstChildElement(subNodeElement, "service");
-                if (actionElement != null) {
-                    actions.add(new ModelTreeAction.Service(this, actionElement));
-                }
-                actionElement = UtilXml.firstChildElement(subNodeElement, "entity-condition");
-                if (actionElement != null) {
-                    actions.add(new ModelTreeAction.EntityCondition(this, actionElement));
-                }
-                actionElement = UtilXml.firstChildElement(subNodeElement, "script");
-                if (actionElement != null) {
-                    actions.add(new ModelTreeAction.Script(this, actionElement));
-                }
-                actions.trimToSize();
-                this.actions = Collections.unmodifiableList(actions);
+            public Image(Element imageElement) {
+                this.borderExdr = FlexibleStringExpander
+                        .getInstance(UtilXml.checkEmpty(imageElement.getAttribute("border"), "0"));
+                this.heightExdr = FlexibleStringExpander.getInstance(imageElement.getAttribute("height"));
+                this.idExdr = FlexibleStringExpander.getInstance(imageElement.getAttribute("id"));
+                this.srcExdr = FlexibleStringExpander.getInstance(imageElement.getAttribute("src"));
+                this.styleExdr = FlexibleStringExpander.getInstance(imageElement.getAttribute("style"));
+                this.urlMode = UtilXml.checkEmpty(imageElement.getAttribute("url-mode"), "content");
+                this.widthExdr = FlexibleStringExpander.getInstance(imageElement.getAttribute("width"));
             }
 
-            public ModelTree.ModelNode getNode() {
-                return this.rootNode;
+            public String getBorder(Map<String, Object> context) {
+                return this.borderExdr.expandString(context);
             }
 
-            public String getNodeName(Map<String, Object> context) {
-                return this.nodeNameExdr.expandString(context);
+            public String getHeight(Map<String, Object> context) {
+                return this.heightExdr.expandString(context);
             }
 
-            public List<ModelWidgetAction> getActions() {
-                return actions;
+            public String getId(Map<String, Object> context) {
+                return this.idExdr.expandString(context);
             }
 
-            public void setListIterator(ListIterator<? extends Map<String, ? extends Object>> iter) {
-                listIterator = iter;
+            public String getSrc(Map<String, Object> context) {
+                return this.srcExdr.expandString(context);
             }
 
-            public ListIterator<? extends Map<String, ? extends Object>> getListIterator() {
-                return listIterator;
+            public String getStyle(Map<String, Object> context) {
+                return this.styleExdr.expandString(context);
             }
 
-            @Override
-            public void accept(ModelWidgetVisitor visitor) throws Exception {
-                visitor.visit(this);
+            public String getUrlMode() {
+                return this.urlMode;
             }
-        }
-
-        public static class Label {
-            protected FlexibleStringExpander textExdr;
-
-            protected FlexibleStringExpander idExdr;
-            protected FlexibleStringExpander styleExdr;
-
-            public Label(Element labelElement) {
-
-                // put the text attribute first, then the pcdata under the element, if both are there of course
-                String textAttr = UtilFormatOut.checkNull(labelElement.getAttribute("text"));
-                String pcdata = UtilFormatOut.checkNull(UtilXml.elementValue(labelElement));
-                this.textExdr = FlexibleStringExpander.getInstance(textAttr + pcdata);
 
-                this.idExdr = FlexibleStringExpander.getInstance(labelElement.getAttribute("id"));
-                this.styleExdr = FlexibleStringExpander.getInstance(labelElement.getAttribute("style"));
+            public String getWidth(Map<String, Object> context) {
+                return this.widthExdr.expandString(context);
             }
 
-            public void renderLabelString(Appendable writer, Map<String, Object> context, TreeStringRenderer treeStringRenderer) {
+            public void renderImageString(Appendable writer, Map<String, Object> context, TreeStringRenderer treeStringRenderer) {
                 try {
-                    treeStringRenderer.renderLabel(writer, context, this);
+                    treeStringRenderer.renderImage(writer, context, this);
                 } catch (IOException e) {
-                    String errMsg = "Error rendering label with id [" + getId(context) + "]: " + e.toString();
+                    String errMsg = "Error rendering image with id [" + getId(context) + "]: " + e.toString();
                     Debug.logError(e, errMsg, module);
                     throw new RuntimeException(errMsg);
                 }
             }
+        }
 
-            public String getText(Map<String, Object> context) {
-                String text = this.textExdr.expandString(context);
-                StringUtil.SimpleEncoder simpleEncoder = (StringUtil.SimpleEncoder) context.get("simpleEncoder");
-                if (simpleEncoder != null) {
-                    text = simpleEncoder.encode(text);
-                }
-                return text;
+        /**
+         * Models the &lt;label&gt; element.
+         * 
+         * @see <code>widget-tree.xsd</code>
+         */
+        public static final class Label {
+            private final FlexibleStringExpander idExdr;
+            private final FlexibleStringExpander styleExdr;
+            private final FlexibleStringExpander textExdr;
+
+            public Label(Element labelElement) {
+                String textAttr = labelElement.getAttribute("text");
+                String pcdata = UtilXml.checkEmpty(UtilXml.elementValue(labelElement), "");
+                this.textExdr = FlexibleStringExpander.getInstance(textAttr + pcdata);
+                this.idExdr = FlexibleStringExpander.getInstance(labelElement.getAttribute("id"));
+                this.styleExdr = FlexibleStringExpander.getInstance(labelElement.getAttribute("style"));
             }
 
             public String getId(Map<String, Object> context) {
@@ -795,136 +749,111 @@ public class ModelTree extends ModelWidg
             public String getStyle(Map<String, Object> context) {
                 return this.styleExdr.expandString(context);
             }
-        }
-
-
-        public static class Link {
-            protected FlexibleStringExpander textExdr;
-            protected FlexibleStringExpander idExdr;
-            protected FlexibleStringExpander styleExdr;
-            protected FlexibleStringExpander targetExdr;
-            protected FlexibleStringExpander targetWindowExdr;
-            protected FlexibleStringExpander prefixExdr;
-            protected FlexibleStringExpander nameExdr;
-            protected FlexibleStringExpander titleExdr;
-            protected Image image;
-            protected String urlMode = "intra-app";
-            protected boolean fullPath = false;
-            protected boolean secure = false;
-            protected boolean encode = false;
-            protected String linkType;
-            protected List<WidgetWorker.Parameter> parameterList = new ArrayList<WidgetWorker.Parameter>();
-
-            public Link() {
-                setText(null);
-                setId(null);
-                setStyle(null);
-                setTarget(null);
-                setTargetWindow(null);
-                setPrefix(null);
-                setUrlMode(null);
-                setFullPath(null);
-                setSecure(null);
-                setEncode(null);
-                setName(null);
-                setTitle(null);
-            }
-
-            public Link(Element linkElement) {
-
-                setText(linkElement.getAttribute("text"));
-                setId(linkElement.getAttribute("id"));
-                setStyle(linkElement.getAttribute("style"));
-                setTarget(linkElement.getAttribute("target"));
-                setTargetWindow(linkElement.getAttribute("target-window"));
-                setPrefix(linkElement.getAttribute("prefix"));
-                setUrlMode(linkElement.getAttribute("url-mode"));
-                setFullPath(linkElement.getAttribute("full-path"));
-                setSecure(linkElement.getAttribute("secure"));
-                setEncode(linkElement.getAttribute("encode"));
-                setName(linkElement.getAttribute("name"));
-                setTitle(linkElement.getAttribute("title"));
-                Element imageElement = UtilXml.firstChildElement(linkElement, "image");
-                if (imageElement != null) {
-                    this.image = new Image(imageElement);
-                }
-                this.linkType = linkElement.getAttribute("link-type");
-                List<? extends Element> parameterElementList = UtilXml.childElementList(linkElement, "parameter");
-                for (Element parameterElement: parameterElementList) {
-                    this.parameterList.add(new WidgetWorker.Parameter(parameterElement));
-                }
-            }
-
-            public void renderLinkString(Appendable writer, Map<String, Object> context, TreeStringRenderer treeStringRenderer) {
-                try {
-                    treeStringRenderer.renderLink(writer, context, this);
-                } catch (IOException e) {
-                    String errMsg = "Error rendering link with id [" + getId(context) + "]: " + e.toString();
-                    Debug.logError(e, errMsg, module);
-                    throw new RuntimeException(errMsg);
-                }
-            }
 
             public String getText(Map<String, Object> context) {
                 String text = this.textExdr.expandString(context);
-                StringUtil.SimpleEncoder simpleEncoder = (StringUtil.SimpleEncoder) context.get("simpleEncoder");
+                // FIXME: Encoding should be done by the renderer, not by the model.
+                UtilCodec.SimpleEncoder simpleEncoder = (UtilCodec.SimpleEncoder) context.get("simpleEncoder");
                 if (simpleEncoder != null) {
                     text = simpleEncoder.encode(text);
                 }
                 return text;
             }
 
-            public String getId(Map<String, Object> context) {
-                return this.idExdr.expandString(context);
+            public void renderLabelString(Appendable writer, Map<String, Object> context, TreeStringRenderer treeStringRenderer) {
+                try {
+                    treeStringRenderer.renderLabel(writer, context, this);
+                } catch (IOException e) {
+                    String errMsg = "Error rendering label with id [" + getId(context) + "]: " + e.toString();
+                    Debug.logError(e, errMsg, module);
+                    throw new RuntimeException(errMsg);
+                }
             }
+        }
 
-            public String getStyle(Map<String, Object> context) {
-                return this.styleExdr.expandString(context);
-            }
+        /**
+         * Models the &lt;link&gt; element.
+         * 
+         * @see <code>widget-tree.xsd</code>
+         */
+        public static class Link {
+            private final boolean encode;
+            private final boolean fullPath;
+            private final FlexibleStringExpander idExdr;
+            private final Image image;
+            private final String linkType;
+            private final FlexibleStringExpander nameExdr;
+            private final List<Parameter> parameterList;
+            private final FlexibleStringExpander prefixExdr;
+            private final boolean secure;
+            private final FlexibleStringExpander styleExdr;
+            private final FlexibleStringExpander targetExdr;
+            private final FlexibleStringExpander targetWindowExdr;
+            private final FlexibleStringExpander textExdr;
+            private final FlexibleStringExpander titleExdr;
+            private final String urlMode;
 
-            public String getName(Map<String, Object> context) {
-                return this.nameExdr.expandString(context);
-            }
-            public String getTitle(Map<String, Object> context) {
-                String title = this.titleExdr.expandString(context);
-                StringUtil.SimpleEncoder simpleEncoder = (StringUtil.SimpleEncoder) context.get("simpleEncoder");
-                if (simpleEncoder != null) {
-                    title = simpleEncoder.encode(title);
+            public Link(Element linkElement) {
+                this.encode = "true".equals(linkElement.getAttribute("encode"));
+                this.fullPath = "true".equals(linkElement.getAttribute("full-path"));
+                this.idExdr = FlexibleStringExpander.getInstance(linkElement.getAttribute("id"));
+                Element imageElement = UtilXml.firstChildElement(linkElement, "image");
+                if (imageElement != null) {
+                    this.image = new Image(imageElement);
+                } else {
+                    this.image = null;
                 }
-                return title;
-            }
-
-            public String getTarget(Map<String, Object> context) {
-                StringUtil.SimpleEncoder simpleEncoder = (StringUtil.SimpleEncoder) context.get("simpleEncoder");
-                if (simpleEncoder != null) {
-                    return this.targetExdr.expandString(StringUtil.HtmlEncodingMapWrapper.getHtmlEncodingMapWrapper(context, simpleEncoder));
+                this.linkType = linkElement.getAttribute("link-type");
+                this.nameExdr = FlexibleStringExpander.getInstance(linkElement.getAttribute("name"));
+                List<? extends Element> parameterElementList = UtilXml.childElementList(linkElement, "parameter");
+                if (!parameterElementList.isEmpty()) {
+                    List<Parameter> parameterList = new ArrayList<Parameter>(parameterElementList.size());
+                    for (Element parameterElement : parameterElementList) {
+                        parameterList.add(new Parameter(parameterElement));
+                    }
+                    this.parameterList = Collections.unmodifiableList(parameterList);
                 } else {
-                    return this.targetExdr.expandString(context);
+                    this.parameterList = Collections.emptyList();
                 }
+                this.prefixExdr = FlexibleStringExpander.getInstance(linkElement.getAttribute("prefix"));
+                this.secure = "true".equals(linkElement.getAttribute("secure"));
+                this.styleExdr = FlexibleStringExpander.getInstance(linkElement.getAttribute("style"));
+                this.targetExdr = FlexibleStringExpander.getInstance(linkElement.getAttribute("target"));
+                this.targetWindowExdr = FlexibleStringExpander.getInstance(linkElement.getAttribute("target-window"));
+                this.textExdr = FlexibleStringExpander.getInstance(linkElement.getAttribute("text"));
+                this.titleExdr = FlexibleStringExpander.getInstance(linkElement.getAttribute("title"));
+                this.urlMode = UtilXml.checkEmpty(linkElement.getAttribute("link-type"), "intra-app");
+            }
+
+            // FIXME: Using a widget model in this way is an ugly hack.
+            public Link(String style, String target, String text) {
+                this.encode = false;
+                this.fullPath = false;
+                this.idExdr = FlexibleStringExpander.getInstance("");
+                this.image = null;
+                this.linkType = "";
+                this.nameExdr = FlexibleStringExpander.getInstance("");
+                this.parameterList = Collections.emptyList();
+                this.prefixExdr = FlexibleStringExpander.getInstance("");
+                this.secure = false;
+                this.styleExdr = FlexibleStringExpander.getInstance(style);
+                this.targetExdr = FlexibleStringExpander.getInstance(target);
+                this.targetWindowExdr = FlexibleStringExpander.getInstance("");
+                this.textExdr = FlexibleStringExpander.getInstance(text);
+                this.titleExdr = FlexibleStringExpander.getInstance("");
+                this.urlMode = "intra-app";
             }
 
-            public String getTargetWindow(Map<String, Object> context) {
-                return this.targetWindowExdr.expandString(context);
-            }
-
-            public String getUrlMode() {
-                return this.urlMode;
-            }
-
-            public String getPrefix(Map<String, Object> context) {
-                return this.prefixExdr.expandString(context);
+            public boolean getEncode() {
+                return this.encode;
             }
 
             public boolean getFullPath() {
                 return this.fullPath;
             }
 
-            public boolean getSecure() {
-                return this.secure;
-            }
-
-            public boolean getEncode() {
-                return this.encode;
+            public String getId(Map<String, Object> context) {
+                return this.idExdr.expandString(context);
             }
 
             public Image getImage() {
@@ -935,188 +864,154 @@ public class ModelTree extends ModelWidg
                 return this.linkType;
             }
 
+            public String getName(Map<String, Object> context) {
+                return this.nameExdr.expandString(context);
+            }
+
             public Map<String, String> getParameterMap(Map<String, Object> context) {
                 Map<String, String> fullParameterMap = new HashMap<String, String>();
-
                 /* leaving this here... may want to add it at some point like the hyperlink element:
                 Map<String, String> addlParamMap = this.parametersMapAcsr.get(context);
                 if (addlParamMap != null) {
                     fullParameterMap.putAll(addlParamMap);
                 }
                 */
-                
-                for (WidgetWorker.Parameter parameter: this.parameterList) {
+                for (WidgetWorker.Parameter parameter : this.parameterList) {
                     fullParameterMap.put(parameter.getName(), parameter.getValue(context));
                 }
-                
                 return fullParameterMap;
             }
 
-            public void setText(String val) {
-                String textAttr = UtilFormatOut.checkNull(val);
-                this.textExdr = FlexibleStringExpander.getInstance(textAttr);
-            }
-            public void setId(String val) {
-                this.idExdr = FlexibleStringExpander.getInstance(val);
-            }
-            public void setStyle(String val) {
-                this.styleExdr = FlexibleStringExpander.getInstance(val);
-            }
-            public void setName(String val) {
-                this.nameExdr = FlexibleStringExpander.getInstance(val);
-            }
-            public void setTitle(String val) {
-                this.titleExdr = FlexibleStringExpander.getInstance(val);
-            }
-            public void setTarget(String val) {
-                this.targetExdr = FlexibleStringExpander.getInstance(val);
-            }
-            public void setTargetWindow(String val) {
-                this.targetWindowExdr = FlexibleStringExpander.getInstance(val);
-            }
-            public void setPrefix(String val) {
-                this.prefixExdr = FlexibleStringExpander.getInstance(val);
-            }
-            public void setUrlMode(String val) {
-                if (UtilValidate.isNotEmpty(val))
-                    this.urlMode = val;
-            }
-            public void setFullPath(String val) {
-                String sFullPath = val;
-                if (sFullPath != null && sFullPath.equalsIgnoreCase("true"))
-                    this.fullPath = true;
-                else
-                    this.fullPath = false;
+            public String getPrefix(Map<String, Object> context) {
+                return this.prefixExdr.expandString(context);
             }
 
-            public void setSecure(String val) {
-                String sSecure = val;
-                if (sSecure != null && sSecure.equalsIgnoreCase("true"))
-                    this.secure = true;
-                else
-                    this.secure = false;
+            public boolean getSecure() {
+                return this.secure;
             }
 
-            public void setEncode(String val) {
-                String sEncode = val;
-                if (sEncode != null && sEncode.equalsIgnoreCase("true"))
-                    this.encode = true;
-                else
-                    this.encode = false;
-            }
-            public void setImage(Image img) {
-                this.image = img;
+            public String getStyle(Map<String, Object> context) {
+                return this.styleExdr.expandString(context);
             }
 
-        }
-
-        public static class Image {
+            public String getTarget(Map<String, Object> context) {
+                UtilCodec.SimpleEncoder simpleEncoder = (UtilCodec.SimpleEncoder) context.get("simpleEncoder");
+                if (simpleEncoder != null) {
+                    return this.targetExdr.expandString(UtilCodec.HtmlEncodingMapWrapper.getHtmlEncodingMapWrapper(context,
+                            simpleEncoder));
+                } else {
+                    return this.targetExdr.expandString(context);
+                }
+            }
 
-            protected FlexibleStringExpander srcExdr;
-            protected FlexibleStringExpander idExdr;
-            protected FlexibleStringExpander styleExdr;
-            protected FlexibleStringExpander widthExdr;
-            protected FlexibleStringExpander heightExdr;
-            protected FlexibleStringExpander borderExdr;
-            protected String urlMode;
-
-            public Image() {
-
-                setSrc(null);
-                setId(null);
-                setStyle(null);
-                setWidth(null);
-                setHeight(null);
-                setBorder("0");
-                setUrlMode(null);
+            public String getTargetWindow(Map<String, Object> context) {
+                return this.targetWindowExdr.expandString(context);
             }
 
-            public Image(Element imageElement) {
+            public String getText(Map<String, Object> context) {
+                String text = this.textExdr.expandString(context);
+                // FIXME: Encoding should be done by the renderer, not by the model.
+                UtilCodec.SimpleEncoder simpleEncoder = (UtilCodec.SimpleEncoder) context.get("simpleEncoder");
+                if (simpleEncoder != null) {
+                    text = simpleEncoder.encode(text);
+                }
+                return text;
+            }
 
-                setSrc(imageElement.getAttribute("src"));
-                setId(imageElement.getAttribute("id"));
-                setStyle(imageElement.getAttribute("style"));
-                setWidth(imageElement.getAttribute("width"));
-                setHeight(imageElement.getAttribute("height"));
-                setBorder(UtilFormatOut.checkEmpty(imageElement.getAttribute("border"), "0"));
-                setUrlMode(UtilFormatOut.checkEmpty(imageElement.getAttribute("url-mode"), "content"));
+            public String getTitle(Map<String, Object> context) {
+                String title = this.titleExdr.expandString(context);
+                // FIXME: Encoding should be done by the renderer, not by the model.
+                UtilCodec.SimpleEncoder simpleEncoder = (UtilCodec.SimpleEncoder) context.get("simpleEncoder");
+                if (simpleEncoder != null) {
+                    title = simpleEncoder.encode(title);
+                }
+                return title;
+            }
 
+            public String getUrlMode() {
+                return this.urlMode;
             }
 
-            public void renderImageString(Appendable writer, Map<String, Object> context, TreeStringRenderer treeStringRenderer) {
+            public void renderLinkString(Appendable writer, Map<String, Object> context, TreeStringRenderer treeStringRenderer) {
                 try {
-                    treeStringRenderer.renderImage(writer, context, this);
+                    treeStringRenderer.renderLink(writer, context, this);
                 } catch (IOException e) {
-                    String errMsg = "Error rendering image with id [" + getId(context) + "]: " + e.toString();
+                    String errMsg = "Error rendering link with id [" + getId(context) + "]: " + e.toString();
                     Debug.logError(e, errMsg, module);
                     throw new RuntimeException(errMsg);
                 }
             }
+        }
 
-            public String getSrc(Map<String, Object> context) {
-                return this.srcExdr.expandString(context);
-            }
+        /**
+         * Models the &lt;sub-node&gt; element.
+         * 
+         * @see <code>widget-tree.xsd</code>
+         */
+        public static class ModelSubNode extends ModelWidget {
 
-            public String getId(Map<String, Object> context) {
-                return this.idExdr.expandString(context);
-            }
+            private final List<ModelWidgetAction> actions;
+            private final FlexibleStringExpander nodeNameExdr;
+            private final ModelNode rootNode;
+            private final String iteratorKey;
 
-            public String getStyle(Map<String, Object> context) {
-                return this.styleExdr.expandString(context);
+            public ModelSubNode(Element subNodeElement, ModelNode modelNode) {
+                super(subNodeElement);
+                this.rootNode = modelNode;
+                this.nodeNameExdr = FlexibleStringExpander.getInstance(subNodeElement.getAttribute("node-name"));
+                ArrayList<ModelWidgetAction> actions = new ArrayList<ModelWidgetAction>();
+                // FIXME: Validate child elements, should be only one of actions, entity-and, entity-condition, service, script.
+                Element actionsElement = UtilXml.firstChildElement(subNodeElement, "actions");
+                if (actionsElement != null) {
+                    actions.addAll(ModelTreeAction.readSubNodeActions(this, actionsElement));
+                }
+                Element actionElement = UtilXml.firstChildElement(subNodeElement, "entity-and");
+                if (actionElement != null) {
+                    actions.add(new ModelTreeAction.EntityAnd(this, actionElement));
+                }
+                actionElement = UtilXml.firstChildElement(subNodeElement, "service");
+                if (actionElement != null) {
+                    actions.add(new ModelTreeAction.Service(this, actionElement));
+                }
+                actionElement = UtilXml.firstChildElement(subNodeElement, "entity-condition");
+                if (actionElement != null) {
+                    actions.add(new ModelTreeAction.EntityCondition(this, actionElement));
+                }
+                actionElement = UtilXml.firstChildElement(subNodeElement, "script");
+                if (actionElement != null) {
+                    actions.add(new ModelTreeAction.Script(this, actionElement));
+                }
+                actions.trimToSize();
+                this.actions = Collections.unmodifiableList(actions);
+                this.iteratorKey = this.rootNode.getName().concat(".").concat(this.nodeNameExdr.getOriginal())
+                        .concat(".ITERATOR");
             }
 
-            public String getWidth(Map<String, Object> context) {
-                return this.widthExdr.expandString(context);
+            @Override
+            public void accept(ModelWidgetVisitor visitor) throws Exception {
+                visitor.visit(this);
             }
 
-            public String getHeight(Map<String, Object> context) {
-                return this.heightExdr.expandString(context);
+            public List<ModelWidgetAction> getActions() {
+                return actions;
             }
 
-            public String getBorder(Map<String, Object> context) {
-                return this.borderExdr.expandString(context);
+            @SuppressWarnings("unchecked")
+            public ListIterator<? extends Map<String, ? extends Object>> getListIterator(Map<String, Object> context) {
+                return (ListIterator<? extends Map<String, ? extends Object>>) context.get(this.iteratorKey);
             }
 
-            public String getUrlMode() {
-                return this.urlMode;
+            public ModelTree.ModelNode getNode() {
+                return this.rootNode;
             }
 
-            public void setSrc(String val) {
-                String textAttr = UtilFormatOut.checkNull(val);
-                this.srcExdr = FlexibleStringExpander.getInstance(textAttr);
-            }
-            public void setId(String val) {
-                this.idExdr = FlexibleStringExpander.getInstance(val);
-            }
-            public void setStyle(String val) {
-                this.styleExdr = FlexibleStringExpander.getInstance(val);
-            }
-            public void setWidth(String val) {
-                this.widthExdr = FlexibleStringExpander.getInstance(val);
-            }
-            public void setHeight(String val) {
-                this.heightExdr = FlexibleStringExpander.getInstance(val);
-            }
-            public void setBorder(String val) {
-                this.borderExdr = FlexibleStringExpander.getInstance(val);
-            }
-            public void setUrlMode(String val) {
-                if (UtilValidate.isEmpty(val)) {
-                    this.urlMode = "content";
-                } else {
-                    this.urlMode = val;
-                }
+            public String getNodeName(Map<String, Object> context) {
+                return this.nodeNameExdr.expandString(context);
             }
-        }
 
-        @Override
-        public void accept(ModelWidgetVisitor visitor) throws Exception {
-            visitor.visit(this);
+            public void setListIterator(ListIterator<? extends Map<String, ? extends Object>> iter, Map<String, Object> context) {
+                context.put(this.iteratorKey, iter);
+            }
         }
     }
-
-    @Override
-    public void accept(ModelWidgetVisitor visitor) throws Exception {
-        visitor.visit(this);
-    }
 }