| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| |
| package org.apache.webapp.admin; |
| |
| |
| import java.io.IOException; |
| import java.net.URLEncoder; |
| import javax.servlet.http.HttpServletResponse; |
| import javax.servlet.jsp.JspException; |
| import javax.servlet.jsp.JspWriter; |
| import javax.servlet.jsp.PageContext; |
| import javax.servlet.jsp.tagext.TagSupport; |
| |
| |
| /** |
| * <p>JSP custom tag that renders a tree control represented by the |
| * <code>TreeControl</code> and <code>TreeControlNode</code> classes. |
| * This tag has the following user-settable attributes:</p> |
| * <ul> |
| * <li><strong>action</strong> - Hyperlink to which expand/contract actions |
| * should be sent, with a string "<code>{name}</code> marking where |
| * the node name of the affected node should be included.</li> |
| * <li><strong>images</strong> - Name of the directory containing the images |
| * for our icons, relative to the page including this tag. If not |
| * specified, defaults to "images".</li> |
| * <li><strong>scope</strong> - Attribute scope in which the <code>tree</code> |
| * attribute is to be found (page, request, session, application). If |
| * not specified, the attribute is searched for in all scopes.</li> |
| * <li><strong>style</strong> - CSS style <code>class</code> to be applied |
| * to be applied to the entire rendered output of the tree control. |
| * If not specified, no style class is applied.</li> |
| * <li><strong>styleSelected</strong> - CSS style <code>class</code> to be |
| * applied to the text of any element that is currently selected. If not |
| * specified, no additional style class is applied.</li> |
| * <li><strong>styleUnselected</strong> - CSS style <code>class</code> to be |
| * applied to the text of any element that is not currently selected. |
| * If not specified, no additional style class is applied.</li> |
| * <li><strong>tree</strong> - Attribute name under which the |
| * <code>TreeControl</code> bean of the tree we are rendering |
| * is stored, in the scope specified by the <code>scope</code> |
| * attribute. This attribute is required.</li> |
| * </ul> |
| * |
| * <strong>FIXME</strong> - Internationalize the exception messages! |
| * |
| * @author Craig R. McClanahan |
| * @version $Revision$ $Date$ |
| */ |
| |
| public class TreeControlTag extends TagSupport { |
| |
| |
| /** |
| * The default directory name for icon images. |
| */ |
| static final String DEFAULT_IMAGES = "images"; |
| |
| |
| /** |
| * The names of tree state images that we need. |
| */ |
| static final String IMAGE_HANDLE_DOWN_LAST = "handledownlast.gif"; |
| static final String IMAGE_HANDLE_DOWN_MIDDLE = "handledownmiddle.gif"; |
| static final String IMAGE_HANDLE_RIGHT_LAST = "handlerightlast.gif"; |
| static final String IMAGE_HANDLE_RIGHT_MIDDLE = "handlerightmiddle.gif"; |
| static final String IMAGE_LINE_LAST = "linelastnode.gif"; |
| static final String IMAGE_LINE_MIDDLE = "linemiddlenode.gif"; |
| static final String IMAGE_LINE_VERTICAL = "linevertical.gif"; |
| |
| |
| // ------------------------------------------------------------- Properties |
| |
| |
| /** |
| * The hyperlink to be used for submitting requests to expand and |
| * contract tree nodes. The placeholder "<code>{name}</code>" will |
| * be replaced by the <code>name</code> property of the current |
| * tree node. |
| */ |
| protected String action = null; |
| |
| public String getAction() { |
| return (this.action); |
| } |
| |
| public void setAction(String action) { |
| this.action = action; |
| } |
| |
| |
| /** |
| * The name of the directory containing the images for our icons, |
| * relative to the page including this tag. |
| */ |
| protected String images = DEFAULT_IMAGES; |
| |
| public String getImages() { |
| return (this.images); |
| } |
| |
| public void setImages(String images) { |
| this.images = images; |
| } |
| |
| |
| /** |
| * The name of the scope in which to search for the <code>tree</code> |
| * attribute. Must be "page", "request", "session", or "application" |
| * (or <code>null</code> for an ascending-visibility search). |
| */ |
| protected String scope = null; |
| |
| public String getScope() { |
| return (this.scope); |
| } |
| |
| public void setScope(String scope) { |
| if (!"page".equals(scope) && |
| !"request".equals(scope) && |
| !"session".equals(scope) && |
| !"application".equals(scope)) |
| throw new IllegalArgumentException("Invalid scope '" + |
| scope + "'"); |
| this.scope = scope; |
| } |
| |
| |
| /** |
| * The CSS style <code>class</code> to be applied to the entire tree. |
| */ |
| protected String style = null; |
| |
| public String getStyle() { |
| return (this.style); |
| } |
| |
| public void setStyle(String style) { |
| this.style = style; |
| } |
| |
| |
| /** |
| * The CSS style <code>class</code> to be applied to the text |
| * of selected nodes. |
| */ |
| protected String styleSelected = null; |
| |
| public String getStyleSelected() { |
| return (this.styleSelected); |
| } |
| |
| public void setStyleSelected(String styleSelected) { |
| this.styleSelected = styleSelected; |
| } |
| |
| |
| /** |
| * The CSS style <code>class</code> to be applied to the text |
| * of unselected nodes. |
| */ |
| protected String styleUnselected = null; |
| |
| public String getStyleUnselected() { |
| return (this.styleUnselected); |
| } |
| |
| public void setStyleUnselected(String styleUnselected) { |
| this.styleUnselected = styleUnselected; |
| } |
| |
| |
| /** |
| * The name of the attribute (in the specified scope) under which our |
| * <code>TreeControl</code> instance is stored. |
| */ |
| protected String tree = null; |
| |
| public String getTree() { |
| return (this.tree); |
| } |
| |
| public void setTree(String tree) { |
| this.tree = tree; |
| } |
| |
| |
| // --------------------------------------------------------- Public Methods |
| |
| |
| /** |
| * Render this tree control. |
| * |
| * @exception JspException if a processing error occurs |
| */ |
| public int doEndTag() throws JspException { |
| |
| TreeControl treeControl = getTreeControl(); |
| JspWriter out = pageContext.getOut(); |
| try { |
| out.print |
| ("<table border=\"0\" cellspacing=\"0\" cellpadding=\"0\""); |
| if (style != null) { |
| out.print(" class=\""); |
| out.print(style); |
| out.print("\""); |
| } |
| out.println(">"); |
| int level = 0; |
| TreeControlNode node = treeControl.getRoot(); |
| render(out, node, level, treeControl.getWidth(), true); |
| out.println("</table>"); |
| } catch (IOException e) { |
| throw new JspException(e); |
| } |
| |
| return (EVAL_PAGE); |
| |
| } |
| |
| |
| /** |
| * Release all state information set by this tag. |
| */ |
| public void release() { |
| |
| this.action = null; |
| this.images = DEFAULT_IMAGES; |
| this.scope = null; |
| this.style = null; |
| this.styleSelected = null; |
| this.styleUnselected = null; |
| this.tree = null; |
| |
| } |
| |
| |
| // ------------------------------------------------------ Protected Methods |
| |
| |
| /** |
| * Return the <code>TreeControl</code> instance for the tree control that |
| * we are rendering. |
| * |
| * @exception JspException if no TreeControl instance can be found |
| */ |
| protected TreeControl getTreeControl() throws JspException { |
| |
| Object treeControl = null; |
| if (scope == null) |
| treeControl = pageContext.findAttribute(tree); |
| else if ("page".equals(scope)) |
| treeControl = |
| pageContext.getAttribute(tree, PageContext.PAGE_SCOPE); |
| else if ("request".equals(scope)) |
| treeControl = |
| pageContext.getAttribute(tree, PageContext.REQUEST_SCOPE); |
| else if ("session".equals(scope)) |
| treeControl = |
| pageContext.getAttribute(tree, PageContext.SESSION_SCOPE); |
| else if ("application".equals(scope)) |
| treeControl = |
| pageContext.getAttribute(tree, PageContext.APPLICATION_SCOPE); |
| if (treeControl == null) |
| throw new JspException("Cannot find tree control attribute '" + |
| tree + "'"); |
| else if (!(treeControl instanceof TreeControl)) |
| throw new JspException("Invalid tree control attribute '" + |
| tree + "'"); |
| else |
| return ((TreeControl) treeControl); |
| |
| } |
| |
| |
| /** |
| * Render the specified node, as controlled by the specified parameters. |
| * |
| * @param out The <code>JspWriter</code> to which we are writing |
| * @param node The <code>TreeControlNode</code> we are currently |
| * rendering |
| * @param level The indentation level of this node in the tree |
| * @param width Total displayable width of the tree |
| * @param last Is this the last node in a list? |
| * |
| * @exception IOException if an input/output error occurs |
| */ |
| protected void render(JspWriter out, TreeControlNode node, |
| int level, int width, boolean last) |
| throws IOException { |
| |
| HttpServletResponse response = |
| (HttpServletResponse) pageContext.getResponse(); |
| |
| // if the node is root node and the label value is |
| // null, then do not render root node in the tree. |
| |
| if ("ROOT-NODE".equalsIgnoreCase(node.getName()) && |
| (node.getLabel() == null)) { |
| // Render the children of this node |
| TreeControlNode children[] = node.findChildren(); |
| int lastIndex = children.length - 1; |
| int newLevel = level + 1; |
| for (int i = 0; i < children.length; i++) { |
| render(out, children[i], newLevel, width, i == lastIndex); |
| } |
| return; |
| } |
| |
| // Render the beginning of this node |
| out.println(" <tr valign=\"middle\">"); |
| |
| // Create the appropriate number of indents |
| for (int i = 0; i < level; i++) { |
| int levels = level - i; |
| TreeControlNode parent = node; |
| for (int j = 1; j <= levels; j++) |
| parent = parent.getParent(); |
| if (parent.isLast()) |
| out.print(" <td></td>"); |
| else { |
| out.print(" <td><img src=\""); |
| out.print(images); |
| out.print("/"); |
| out.print(IMAGE_LINE_VERTICAL); |
| out.print("\" alt=\"\" border=\"0\"></td>"); |
| } |
| out.println(); |
| } |
| |
| // Render the tree state image for this node |
| |
| // HACK to take into account special characters like = and & |
| // in the node name, could remove this code if encode URL |
| // and later request.getParameter() could deal with = and & |
| // character in parameter values. |
| String encodedNodeName = URLEncoder.encode(node.getName(),TomcatTreeBuilder.URL_ENCODING); |
| |
| String action = replace(getAction(), "{name}", encodedNodeName); |
| |
| |
| String updateTreeAction = |
| replace(getAction(), "tree={name}", "select=" + encodedNodeName); |
| updateTreeAction = |
| ((HttpServletResponse) pageContext.getResponse()). |
| encodeURL(updateTreeAction); |
| |
| out.print(" <td>"); |
| if ((action != null) && !node.isLeaf()) { |
| out.print("<a href=\""); |
| out.print(response.encodeURL(action)); |
| out.print("\">"); |
| } |
| out.print("<img src=\""); |
| out.print(images); |
| out.print("/"); |
| if (node.isLeaf()) { |
| if (node.isLast()) |
| out.print(IMAGE_LINE_LAST); |
| else |
| out.print(IMAGE_LINE_MIDDLE); |
| out.print("\" alt=\""); |
| } else if (node.isExpanded()) { |
| if (node.isLast()) |
| out.print(IMAGE_HANDLE_DOWN_LAST); |
| else |
| out.print(IMAGE_HANDLE_DOWN_MIDDLE); |
| out.print("\" alt=\"close node"); |
| } else { |
| if (node.isLast()) |
| out.print(IMAGE_HANDLE_RIGHT_LAST); |
| else |
| out.print(IMAGE_HANDLE_RIGHT_MIDDLE); |
| out.print("\" alt=\"expand node"); |
| } |
| out.print("\" border=\"0\">"); |
| if ((action != null) && !node.isLeaf()) |
| out.print("</a>"); |
| out.println("</td>"); |
| |
| // Calculate the hyperlink for this node (if any) |
| String hyperlink = null; |
| if (node.getAction() != null) |
| hyperlink = ((HttpServletResponse) pageContext.getResponse()). |
| encodeURL(node.getAction()); |
| |
| // Render the icon for this node (if any) |
| out.print(" <td colspan=\""); |
| out.print(width - level + 1); |
| out.print("\">"); |
| if (node.getIcon() != null) { |
| if (hyperlink != null) { |
| out.print("<a href=\""); |
| out.print(hyperlink); |
| out.print("\""); |
| String target = node.getTarget(); |
| if(target != null) { |
| out.print(" target=\""); |
| out.print(target); |
| out.print("\""); |
| } |
| // to refresh the tree in the same 'self' frame |
| out.print(" onclick=\""); |
| out.print("self.location.href='" + updateTreeAction + "'"); |
| out.print("\""); |
| out.print(">"); |
| } |
| out.print("<img src=\""); |
| out.print(images); |
| out.print("/"); |
| out.print(node.getIcon()); |
| out.print("\" alt=\""); |
| out.print("\" border=\"0\">"); |
| if (hyperlink != null) |
| out.print("</a>"); |
| } |
| |
| // Render the label for this node (if any) |
| |
| if (node.getLabel() != null) { |
| String labelStyle = null; |
| if (node.isSelected() && (styleSelected != null)) |
| labelStyle = styleSelected; |
| else if (!node.isSelected() && (styleUnselected != null)) |
| labelStyle = styleUnselected; |
| if (hyperlink != null) { |
| // Note the leading space so that the text has some space |
| // between it and any preceding images |
| out.print(" <a href=\""); |
| out.print(hyperlink); |
| out.print("\""); |
| String target = node.getTarget(); |
| if(target != null) { |
| out.print(" target=\""); |
| out.print(target); |
| out.print("\""); |
| } |
| if (labelStyle != null) { |
| out.print(" class=\""); |
| out.print(labelStyle); |
| out.print("\""); |
| } |
| // to refresh the tree in the same 'self' frame |
| out.print(" onclick=\""); |
| out.print("self.location.href='" + updateTreeAction + "'"); |
| out.print("\""); |
| out.print(">"); |
| } else if (labelStyle != null) { |
| out.print("<span class=\""); |
| out.print(labelStyle); |
| out.print("\">"); |
| } |
| out.print(node.getLabel()); |
| if (hyperlink != null) |
| out.print("</a>"); |
| else if (labelStyle != null) |
| out.print("</span>"); |
| } |
| out.println("</td>"); |
| |
| // Render the end of this node |
| out.println(" </tr>"); |
| |
| // Render the children of this node |
| if (node.isExpanded()) { |
| TreeControlNode children[] = node.findChildren(); |
| int lastIndex = children.length - 1; |
| int newLevel = level + 1; |
| for (int i = 0; i < children.length; i++) { |
| render(out, children[i], newLevel, width, i == lastIndex); |
| } |
| } |
| |
| } |
| |
| |
| /** |
| * Replace any occurrence of the specified placeholder in the specified |
| * template string with the specified replacement value. |
| * |
| * @param template Pattern string possibly containing the placeholder |
| * @param placeholder Placeholder expression to be replaced |
| * @param value Replacement value for the placeholder |
| */ |
| protected String replace(String template, String placeholder, |
| String value) { |
| |
| if (template == null) |
| return (null); |
| if ((placeholder == null) || (value == null)) |
| return (template); |
| while (true) { |
| int index = template.indexOf(placeholder); |
| if (index < 0) |
| break; |
| StringBuffer temp = new StringBuffer(template.substring(0, index)); |
| temp.append(value); |
| temp.append(template.substring(index + placeholder.length())); |
| template = temp.toString(); |
| } |
| return (template); |
| |
| } |
| |
| |
| } |