View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    * 
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   * 
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  
19  package org.apache.webapp.admin;
20  
21  
22  import java.io.IOException;
23  import java.net.URLEncoder;
24  import javax.servlet.http.HttpServletResponse;
25  import javax.servlet.jsp.JspException;
26  import javax.servlet.jsp.JspWriter;
27  import javax.servlet.jsp.PageContext;
28  import javax.servlet.jsp.tagext.TagSupport;
29  
30  
31  /***
32   * <p>JSP custom tag that renders a tree control represented by the
33   * <code>TreeControl</code> and <code>TreeControlNode</code> classes.
34   * This tag has the following user-settable attributes:</p>
35   * <ul>
36   * <li><strong>action</strong> - Hyperlink to which expand/contract actions
37   *     should be sent, with a string "<code>${node}</code> marking where
38   *     the node name of the affected node should be included.</li>
39   * <li><strong>images</strong> - Name of the directory containing the images
40   *     for our icons, relative to the page including this tag.  If not
41   *     specified, defaults to "images".</li>
42   * <li><strong>scope</strong> - Attribute scope in which the <code>tree</code>
43   *     attribute is to be found (page, request, session, application).  If
44   *     not specified, the attribute is searched for in all scopes.</li>
45   * <li><strong>style</strong> - CSS style <code>class</code> to be applied
46   *     to be applied to the entire rendered output of the tree control.
47   *     If not specified, no style class is applied.</li>
48   * <li><strong>styleSelected</strong> - CSS style <code>class</code> to be
49   *     applied to the text of any element that is currently selected.  If not
50   *     specified, no additional style class is applied.</li>
51   * <li><strong>styleUnselected</strong> - CSS style <code>class</code> to be
52   *     applied to the text of any element that is not currently selected.
53   *     If not specified, no additional style class is applied.</li>
54   * <li><strong>tree</strong> - Attribute name under which the
55   *     <code>TreeControl</code> bean of the tree we are rendering
56   *     is stored, in the scope specified by the <code>scope</code>
57   *     attribute.  This attribute is required.</li>
58   * </ul>
59   *
60   * <strong>FIXME</strong> - Internationalize the exception messages!
61   *
62   * @author Craig R. McClanahan
63   * @version $Revision: 516448 $ $Date: 2007-03-09 17:25:47 +0100 (Fri, 09 Mar 2007) $
64   */
65  
66  public class TreeControlTag extends TagSupport 
67  {
68      private static final long serialVersionUID = 1;    
69  
70  
71      /***
72       * The default directory name for icon images.
73       */
74      static final String DEFAULT_IMAGES = "images";
75  
76  
77      /***
78       * The names of tree state images that we need.
79       */
80      static final String IMAGE_HANDLE_DOWN_LAST =    "handledownlast.gif";
81      static final String IMAGE_HANDLE_DOWN_MIDDLE =  "handledownmiddle.gif";
82      static final String IMAGE_HANDLE_RIGHT_LAST =   "handlerightlast.gif";
83      static final String IMAGE_HANDLE_RIGHT_MIDDLE = "handlerightmiddle.gif";
84      static final String IMAGE_LINE_LAST =           "linelastnode.gif";
85      static final String IMAGE_LINE_MIDDLE =         "linemiddlenode.gif";
86      static final String IMAGE_LINE_VERTICAL =       "linevertical.gif";
87  
88  
89      // ------------------------------------------------------------- Properties
90  
91  
92      /***
93       * The hyperlink to be used for submitting requests to expand and
94       * contract tree nodes.  The placeholder "<code>${name}</code>" will
95       * be replaced by the <code>name</code> property of the current
96       * tree node.
97       */
98      protected String action = null;
99  
100     public String getAction() {
101         return (this.action);
102     }
103 
104     public void setAction(String action) {
105         this.action = action;
106     }
107 
108 
109     /***
110      * The name of the directory containing the images for our icons,
111      * relative to the page including this tag.
112      */
113     protected String images = DEFAULT_IMAGES;
114 
115     public String getImages() {
116         return (this.images);
117     }
118 
119     public void setImages(String images) {
120         this.images = images;
121     }
122 
123 
124     /***
125      * The name of the scope in which to search for the <code>tree</code>
126      * attribute.  Must be "page", "request", "session", or "application"
127      * (or <code>null</code> for an ascending-visibility search).
128      */
129     protected String scope = null;
130 
131     public String getScope() {
132         return (this.scope);
133     }
134 
135     public void setScope(String scope) {
136         if (!"page".equals(scope) &&
137             !"request".equals(scope) &&
138             !"session".equals(scope) &&
139             !"application".equals(scope))
140             throw new IllegalArgumentException("Invalid scope '" +
141                                                scope + "'");
142         this.scope = scope;
143     }
144 
145 
146     /***
147      * The CSS style <code>class</code> to be applied to the entire tree.
148      */
149     protected String style = null;
150 
151     public String getStyle() {
152         return (this.style);
153     }
154 
155     public void setStyle(String style) {
156         this.style = style;
157     }
158 
159 
160     /***
161      * The CSS style <code>class</code> to be applied to the text
162      * of selected nodes.
163      */
164     protected String styleSelected = null;
165 
166     public String getStyleSelected() {
167         return (this.styleSelected);
168     }
169 
170     public void setStyleSelected(String styleSelected) {
171         this.styleSelected = styleSelected;
172     }
173 
174 
175     /***
176      * The CSS style <code>class</code> to be applied to the text
177      * of unselected nodes.
178      */
179     protected String styleUnselected = null;
180 
181     public String getStyleUnselected() {
182         return (this.styleUnselected);
183     }
184 
185     public void setStyleUnselected(String styleUnselected) {
186         this.styleUnselected = styleUnselected;
187     }
188 
189 
190     /***
191      * The name of the attribute (in the specified scope) under which our
192      * <code>TreeControl</code> instance is stored.
193      */
194     protected String tree = null;
195 
196     public String getTree() {
197         return (this.tree);
198     }
199 
200     public void setTree(String tree) {
201         this.tree = tree;
202     }
203 
204 
205     // --------------------------------------------------------- Public Methods
206 
207 
208     /***
209      * Render this tree control.
210      *
211      * @exception JspException if a processing error occurs
212      */
213     public int doEndTag() throws JspException {
214 
215         TreeControl treeControl = getTreeControl();
216         JspWriter out = pageContext.getOut();
217         try {
218             out.print
219                 ("<table border=\"0\" cellspacing=\"0\" cellpadding=\"0\"");
220             if (style != null) {
221                 out.print(" class=\"");
222                 out.print(style);
223                 out.print("\"");
224             }
225             out.println(">");
226             int level = 0;
227             TreeControlNode node = treeControl.getRoot();
228             render(out, node, level, treeControl.getWidth(), true);
229             out.println("</table>");
230         } catch (IOException e) {
231             throw new JspException(e);
232         }
233 
234         return (EVAL_PAGE);
235 
236     }
237 
238 
239     /***
240      * Release all state information set by this tag.
241      */
242     public void release() {
243 
244         this.action = null;
245         this.images = DEFAULT_IMAGES;
246         this.scope = null;
247         this.style = null;
248         this.styleSelected = null;
249         this.styleUnselected = null;
250         this.tree = null;
251 
252     }
253 
254 
255     // ------------------------------------------------------ Protected Methods
256 
257 
258     /***
259      * Return the <code>TreeControl</code> instance for the tree control that
260      * we are rendering.
261      *
262      * @exception JspException if no TreeControl instance can be found
263      */
264     protected TreeControl getTreeControl() throws JspException {
265 
266         Object treeControl = null;
267         if (scope == null)
268             treeControl = pageContext.findAttribute(tree);
269         else if ("page".equals(scope))
270             treeControl =
271                 pageContext.getAttribute(tree, PageContext.PAGE_SCOPE);
272         else if ("request".equals(scope))
273             treeControl =
274                 pageContext.getAttribute(tree, PageContext.REQUEST_SCOPE);
275         else if ("session".equals(scope))
276             treeControl =
277                 pageContext.getAttribute(tree, PageContext.SESSION_SCOPE);
278         else if ("application".equals(scope))
279             treeControl =
280                 pageContext.getAttribute(tree, PageContext.APPLICATION_SCOPE);
281         if (treeControl == null)
282             throw new JspException("Cannot find tree control attribute '" +
283                                    tree + "'");
284         else if (!(treeControl instanceof TreeControl))
285             throw new JspException("Invalid tree control attribute '" +
286                                    tree + "'");
287         else
288             return ((TreeControl) treeControl);
289 
290     }
291 
292 
293     /***
294      * Render the specified node, as controlled by the specified parameters.
295      *
296      * @param out The <code>JspWriter</code> to which we are writing
297      * @param node The <code>TreeControlNode</code> we are currently
298      *  rendering
299      * @param level The indentation level of this node in the tree
300      * @param width Total displayable width of the tree
301      * @param last Is this the last node in a list?
302      *
303      * @exception IOException if an input/output error occurs
304      */
305     protected void render(JspWriter out, TreeControlNode node,
306                           int level, int width, boolean last)
307         throws IOException {
308 
309         HttpServletResponse response =
310             (HttpServletResponse) pageContext.getResponse();
311     
312         // if the node is root node and the label value is
313         // null, then do not render root node in the tree.
314         
315         if ("ROOT-NODE".equalsIgnoreCase(node.getName()) &&
316         (node.getLabel() == null)) {
317             // Render the children of this node
318             TreeControlNode children[] = node.findChildren();
319             int lastIndex = children.length - 1;
320             int newLevel = level + 1;
321             for (int i = 0; i < children.length; i++) {
322                 render(out, children[i], newLevel, width, i == lastIndex);
323             }
324             return;
325         }
326         
327         // Render the beginning of this node
328         out.println("  <tr valign=\"middle\">");
329         out.print("<td><table cellspacing=\"0\" cellpadding=\"0\" border=\"0\"><tr valign=\"middle\">");
330 
331         // Create the appropriate number of indents
332         for (int i = 0; i < level; i++) {
333             int levels = level - i;
334             TreeControlNode parent = node;
335             for (int j = 1; j <= levels; j++)
336                 parent = parent.getParent();
337             if (parent.isLast())
338                 out.print("    <td>&nbsp;</td>");
339             else {
340                 out.print("    <td><img src=\"");
341                 out.print(images);
342                 out.print("/");
343                 out.print(IMAGE_LINE_VERTICAL);
344                 out.print("\" alt=\"\" border=\"0\"></td>");
345             }
346             out.println();
347         }
348 
349         // Render the tree state image for this node
350 
351         // HACK to take into account special characters like = and &
352         // in the node name, could remove this code if encode URL
353         // and later request.getParameter() could deal with = and &
354         // character in parameter values. 
355         String encodedNodeName = URLEncoder.encode(node.getName());
356 
357         String action = replace(getAction(), "${name}", encodedNodeName);
358 
359         
360         String updateTreeAction =
361             replace(getAction(), "tree=${name}", "select=" + encodedNodeName);
362         updateTreeAction =
363             ((HttpServletResponse) pageContext.getResponse()).
364             encodeURL(updateTreeAction);
365 
366         out.print("    <td>");
367         
368 //      add an anchor so that we can return to this node
369         out.print("<a name=\"");
370         out.print(node.getName());
371         out.print("\">");
372         
373         if ((action != null) && !node.isLeaf()) {
374             out.print("<a href=\"");
375             out.print(response.encodeURL(action));
376             out.print("\">");
377         }
378         out.print("<img src=\"");
379         out.print(images);
380         out.print("/");
381         if (node.isLeaf()) {
382             if (node.isLast())
383                 out.print(IMAGE_LINE_LAST);
384             else
385                 out.print(IMAGE_LINE_MIDDLE);
386             out.print("\" alt=\"");
387         } else if (node.isExpanded()) {
388             if (node.isLast())
389                 out.print(IMAGE_HANDLE_DOWN_LAST);
390             else
391                 out.print(IMAGE_HANDLE_DOWN_MIDDLE);
392             out.print("\" alt=\"close node");
393         } else {
394             if (node.isLast())
395                 out.print(IMAGE_HANDLE_RIGHT_LAST);
396             else
397                 out.print(IMAGE_HANDLE_RIGHT_MIDDLE);
398             out.print("\" alt=\"expand node");
399         }
400         out.print("\" border=\"0\">");
401         if ((action != null) && !node.isLeaf())
402             out.print("</a>");
403         out.println("</td>");
404 
405         // Calculate the hyperlink for this node (if any)
406         String hyperlink = null;
407         String nodeAction = node.getAction();
408         if(nodeAction == null && node.isExpandWhenClicked())
409         {
410             hyperlink = action;
411         }
412         if (nodeAction != null)
413             hyperlink = ((HttpServletResponse) pageContext.getResponse()).
414                 encodeURL(node.getAction());
415 
416         // Render the icon for this node (if any)
417         out.print("    <td ");
418         
419         if(node.getLabel() != null)
420         {
421             //make sure text does not wrap
422             out.print(" style=\"");
423             out.print("white-space:nowrap; vertical-align:middle;");
424             out.print("\"");
425         }
426         
427         out.print(">");
428         if (node.getIcon() != null) {
429             if (hyperlink != null) {
430                 out.print("<a href=\"");
431                 out.print(hyperlink);
432                 out.print("\"");
433                 String target = node.getTarget();
434                 if(target != null) {
435                     out.print(" target=\"");
436                     out.print(target);
437                     out.print("\"");
438                 }
439                 // to refresh the tree in the same 'self' frame
440                 out.print(" onclick=\"");
441                 out.print("self.location.href='" + updateTreeAction + "'");
442                 out.print("\"");
443                 out.print(">");
444             }
445             out.print("<img src=\"");
446             out.print(images);
447             out.print("/");
448             out.print(node.getIcon());
449             out.print("\" alt=\"");
450             out.print("\" border=\"0\">");
451             if (hyperlink != null)
452                 out.print("</a>");
453         }
454 
455         // Render the label for this node (if any)
456 
457         if (node.getLabel() != null) {
458             String labelStyle = null;
459             if (node.isSelected() && (styleSelected != null))
460                 labelStyle = styleSelected;
461             else if (!node.isSelected() && (styleUnselected != null))
462                 labelStyle = styleUnselected;
463             if (hyperlink != null) {
464                 // Note the leading space so that the text has some space
465                 // between it and any preceding images
466                 out.print(" <a href=\"");
467                 out.print(hyperlink);
468                 out.print("\"");
469                 String target = node.getTarget();
470                 if(target != null) {
471                     out.print(" target=\"");
472                     out.print(target);
473                     out.print("\"");
474                 }
475                 if (labelStyle != null) {
476                     out.print(" class=\"");
477                     out.print(labelStyle);
478                     out.print("\"");
479                 }
480                 
481                 if(node.getTitle() != null)
482                 {
483                     out.print(" title=\"");
484                     out.print(node.getTitle());
485                     out.print("\"");
486                 }
487                 
488                 // to refresh the tree in the same 'self' frame
489                 out.print(" onclick=\"");
490                 out.print("self.location.href='" + updateTreeAction + "'");
491                 out.print("\"");
492                 out.print(">");
493             } else if (labelStyle != null) {
494                 out.print("<span class=\"");
495                 out.print(labelStyle);
496                 out.print("\">");
497             }
498             out.print(node.getLabel());
499             if (hyperlink != null)
500                 out.print("</a>");
501             else if (labelStyle != null)
502                 out.print("</span>");
503         }
504         out.println("</td>");
505 
506         // Render the end of this node
507         out.println("  </tr>");
508         out.println("</table></td></tr>");
509 
510         // Render the children of this node
511         if (node.isExpanded()) {
512             TreeControlNode children[] = node.findChildren();
513             int lastIndex = children.length - 1;
514             int newLevel = level + 1;
515             for (int i = 0; i < children.length; i++) {
516                 render(out, children[i], newLevel, width, i == lastIndex);
517             }
518         }
519 
520     }
521 
522 
523     /***
524      * Replace any occurrence of the specified placeholder in the specified
525      * template string with the specified replacement value.
526      *
527      * @param template Pattern string possibly containing the placeholder
528      * @param placeholder Placeholder expression to be replaced
529      * @param value Replacement value for the placeholder
530      */
531     protected String replace(String template, String placeholder,
532                              String value) {
533 
534         if (template == null)
535             return (null);
536         if ((placeholder == null) || (value == null))
537             return (template);
538         while (true) {
539             int index = template.indexOf(placeholder);
540             if (index < 0)
541                 break;
542             StringBuffer temp = new StringBuffer(template.substring(0, index));
543             temp.append(value);
544             temp.append(template.substring(index + placeholder.length()));
545             template = temp.toString();
546         }
547         return (template);
548 
549     }
550 }