001: /*
002: * Copyright (c) 2001 - 2005 ivata limited.
003: * All rights reserved.
004: * -----------------------------------------------------------------------------
005: * ivata groupware may be redistributed under the GNU General Public
006: * License as published by the Free Software Foundation;
007: * version 2 of the License.
008: *
009: * These programs are free software; you can redistribute them and/or
010: * modify them under the terms of the GNU General Public License
011: * as published by the Free Software Foundation; version 2 of the License.
012: *
013: * These programs are distributed in the hope that they will be useful,
014: * but WITHOUT ANY WARRANTY; without even the implied warranty of
015: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
016: *
017: * See the GNU General Public License in the file LICENSE.txt for more
018: * details.
019: *
020: * If you would like a copy of the GNU General Public License write to
021: *
022: * Free Software Foundation, Inc.
023: * 59 Temple Place - Suite 330
024: * Boston, MA 02111-1307, USA.
025: *
026: *
027: * To arrange commercial support and licensing, contact ivata at
028: * http://www.ivata.com/contact.jsp
029: * -----------------------------------------------------------------------------
030: * $Log: TreeTag.java,v $
031: * Revision 1.3 2005/10/11 18:51:38 colinmacleod
032: * Fixed some checkstyle and javadoc issues.
033: *
034: * Revision 1.2 2005/10/02 14:08:57 colinmacleod
035: * Added/improved log4j logging.
036: *
037: * Revision 1.1 2005/09/29 13:44:03 colinmacleod
038: * Moved to core subproject.
039: *
040: * Revision 1.4 2005/09/14 14:51:43 colinmacleod
041: * Removed unused local and class variables.
042: * Added serialVersionUID.
043: *
044: * Revision 1.3 2005/04/10 18:47:36 colinmacleod
045: * Changed i tag to em and b tag to strong.
046: *
047: * Revision 1.2 2005/04/09 17:19:10 colinmacleod
048: * Changed copyright text to GPL v2 explicitly.
049: *
050: * Revision 1.1.1.1 2005/03/10 17:50:36 colinmacleod
051: * Restructured ivata op around Hibernate/PicoContainer.
052: * Renamed ivata groupware.
053: *
054: * Revision 1.2 2004/11/03 16:10:12 colinmacleod
055: * Changed todo comments to TODO: all caps.
056: *
057: * Revision 1.1 2004/09/30 15:16:03 colinmacleod
058: * Split off addressbook elements into security subproject.
059: *
060: * Revision 1.3 2004/07/13 19:41:15 colinmacleod
061: * Moved project to POJOs from EJBs.
062: * Applied PicoContainer to services layer (replacing session EJBs).
063: * Applied Hibernate to persistence layer (replacing entity EJBs).
064: *
065: * Revision 1.2 2004/03/21 21:16:18 colinmacleod
066: * Shortened name to ivata op.
067: *
068: * Revision 1.1.1.1 2004/01/27 20:57:58 colinmacleod
069: * Moved ivata openportal to SourceForge..
070: *
071: * Revision 1.2 2003/10/17 12:36:13 jano
072: * fixing problems with building
073: * converting intranet -> portal
074: * Eclipse building
075: *
076: * Revision 1.1.1.1 2003/10/13 20:50:14 colin
077: * Restructured portal into subprojects
078: *
079: * Revision 1.1 2003/02/24 19:33:33 colin
080: * moved to jsp
081: *
082: * Revision 1.10 2003/02/04 17:43:51 colin
083: * copyright notice
084: *
085: * Revision 1.9 2003/02/01 12:48:11 colin
086: * improved error handling
087: *
088: * Revision 1.8 2003/01/28 12:57:59 colin
089: * made TreeTag a subclass of ControlTag (rather than WebGuiTag)
090: *
091: * Revision 1.7 2003/01/24 19:31:19 peter
092: * the renderer initialise method was changed...
093: *
094: * Revision 1.6 2002/09/16 14:34:20 jano
095: * added new field formName
096: *
097: * Revision 1.5 2002/08/28 12:54:12 jano
098: * method createChildren changed
099: *
100: * Revision 1.4 2002/08/11 11:57:12 colin
101: * Structural changes to make the design more flexible, for implementing
102: * comment trees.
103: *
104: * Revision 1.3 2002/06/21 12:11:13 colin
105: * restructured com.ivata.groupware.web and split into separate
106: * subcategories
107: *
108: * Revision 1.2 2002/06/13 15:45:15 peter
109: * brought over to peter, fixed bugs in webgui property-settings
110: *
111: * Revision 1.1 2002/06/13 07:44:07 colin
112: * first version of rose model: code tidied up/added javadoc
113: *
114: * Revision 1.5 2002/02/03 15:24:08 colin
115: * changed classname for User to PersonUser
116: *
117: * Revision 1.4 2002/02/02 21:23:01 colin
118: * major restructuring to make the Settings class more generic
119: * all default settings are now taken from the database rather than
120: * being hard coded in the settings class
121: * settings are stored in a HashMap in settings
122: *
123: * Revision 1.3 2002/01/27 19:55:48 colin
124: * updated the themes by removing the multiple section tags and
125: * replacing them with one tag and a Properties instance in
126: * com.ivata.groupware.web.theme.Theme
127: *
128: * Revision 1.2 2002/01/24 13:19:40 colin
129: * consolidated hanlding of theme and properties tags acoss webgui tag
130: * library
131: *
132: * Revision 1.1 2002/01/20 19:28:25 colin
133: * added tab and tree tags
134: * implemented address book functionality
135: * -----------------------------------------------------------------------------
136: */
137: package com.ivata.groupware.web.tag.webgui.tree;
138:
139: import java.io.IOException;
140: import java.util.Properties;
141:
142: import javax.servlet.jsp.JspException;
143: import javax.servlet.jsp.JspWriter;
144: import javax.swing.tree.TreeModel;
145:
146: import org.apache.log4j.Logger;
147:
148: import com.ivata.groupware.web.tree.DefaultTreeNodeRenderer;
149: import com.ivata.groupware.web.tree.TreeNode;
150: import com.ivata.groupware.web.tree.TreeNodeRenderer;
151: import com.ivata.mask.util.CollectionHandling;
152: import com.ivata.mask.web.tag.webgui.ControlTag;
153:
154: /**
155: * <p>Create a tree from a {@link javax.swing.tree.TreeModel TreeModel}.</p>
156: * <p>This tree can be displayed is displayed as an HTML table with links on
157: * each
158: * node.</p>
159: * <p><strong>Tag attributes:</strong><br/>
160: * <table cellpadding='2' cellspacing='5' border='0' align='center'
161: * width='85%'>
162: * <tr class='TableHeadingColor'>
163: * <th>attribute</th>
164: * <th>reqd.</th>
165: * <th>param. class</th>
166: * <th width='100%'>description</th>
167: * </tr>
168: * <tr class='TableRowColor'>
169: * <td>defaultOpen</td>
170: * <td>true</td>
171: * <td>boolean</td>
172: * <td>Set to <code>true</code> if you want tree nodes to be open by
173: * default. Otherwise they will be closed.</td>
174: * </tr>
175: * <tr class='TableRowColor'>
176: * <td>model</td>
177: * <td>true</td>
178: * <td>{@link javax.swing.tree.TreeModel javax.swing.tree.TreeModel}</td>
179: * <td>This model contains the data source for the tree. To use any
180: * datasource
181: * with this tree control, you should first create a class which implements
182: * {@link javax.swing.tree,TreeModel TreeModel}.</td>
183: * </tr>
184: * <tr class='TableRowColor'>
185: * <td>renderer</td>
186: * <td>false</td>
187: * <td>{@link com.ivata.groupware.web.tree.TreeNodeRenderer
188: * com.ivata.groupware.web.tree.TreeNodeRenderer}</td>
189: * <td>This object controls the appearance of each node in the tree,
190: * usually by parsing sections from the {@link
191: * com.ivata.groupware.web.theme.Theme
192: * Theme}.<br/>
193: * If you do not use this attribute, an instance of {@link
194: * com.ivata.groupware.web.DefaultTreeNodeRenderer
195: * DefaultTreeNodeRenderer} is created and applied.</td>
196: * </tr>
197: * <tr class='TableRowColor'>
198: * <td>treeName</td>
199: * <td>true</td>
200: * <td><code>String</code></td>
201: * <td>Specifies a unique identifier for this tree, which is used to store
202: * the state of each foler (open/closed).</td>
203: * </tr>
204: * <tr class='TableRowColor'>
205: * <td>userName</td>
206: * <td>true</td>
207: * <td><code>String</code></td>
208: * <td>Name of the user for whom to draw the tree. The state of each node is
209: * stored for this user and the appearance in recalled the next time the tree is
210: * drawn.</td>
211: * </tr>
212: * </table>
213: * </p>
214: *
215: * @since 2001-12-15
216: * @author Colin MacLeod
217: * <a href='mailto:colin.macleod@ivata.com'>colin.macleod@ivata.com</a>
218: * @version $Revision: 1.3 $
219: */
220: public class TreeTag extends ControlTag {
221: /**
222: * <p>This is the special property used to identify the location
223: * of the children in the open tag.</p>
224: *
225: * <p><strong>Note</strong> that this has to be specified exactly, with
226: * <u>no spaces</u>.</p>
227: */
228: static final String CHILDREN_PROPERTY = "treeChildren";
229:
230: /**
231: * Logger for this class.
232: */
233: private static final Logger logger = Logger
234: .getLogger(TreeTag.class);
235: /**
236: * Serialization version (for <code>Serializable</code> interface).
237: */
238: private static final long serialVersionUID = 1L;
239: /**
240: * <p>Specifies the id of a folder you wish to open.</p>
241: */
242: private Integer closeFolder = null;
243: /**
244: * <p>Decides whether folders should be open or closed by default.</p>
245: */
246: private boolean defaultOpen = false;
247: /**
248: * <p>if you set up name of form it will submit this form when you
249: * open or close folder, otherwise it will make link to same page with
250: * parameters.</p>
251: */
252: private String formName = null;
253: /**
254: * <p>Property declaration for tag attribute: model.</p>
255: */
256: private TreeModel model = null;
257: /**
258: * <p>Specifies the id of a folder you wish to open.</p>
259: */
260: private Integer openFolder = null;
261: /**
262: * <p>This renderer actually draws each node of the tree. If you do
263: * not set a
264: * renderer, then a <code>DefaultTreeNodeRenderer</code> is created
265: * and
266: * applied.</p>
267: */
268: private TreeNodeRenderer renderer;
269: /**
270: * <p>Property declaration for tag attribute: treeName.</p>
271: */
272: private String treeName = null;
273:
274: /**
275: * <p>Default constructor.</p>
276:
277: */
278: public TreeTag() {
279: super ();
280: }
281:
282: /**
283: * <p>This is the method which performs the clever stuff and actually
284: * creates the tree by recursing on itself, for a table mode
285: * tree.</p>
286: *
287: * @param parent the node for which to display all the children.
288: * @param level the depth of this node within the tree, with 0 being
289: * root.
290: * @return the parsed HTML tree as
291: * @throws JspException if there is any <code>IOException</code>.
292: */
293: private String createChildren(final Object parent, final int level)
294: throws JspException {
295: if (logger.isDebugEnabled()) {
296: logger.debug("createChildren(Object parent = " + parent
297: + ", int level = " + level + ") - start");
298: }
299:
300: String returnString = "";
301: int totalNodes = model.getChildCount(parent);
302:
303: for (int nodeNumber = 0; nodeNumber < totalNodes; ++nodeNumber) {
304: // make a fresh copy of the properties for each node
305: Properties nodeProperties = CollectionHandling.splice(
306: getProperties(), new Properties());
307: TreeNode node = (TreeNode) model.getChild(parent,
308: nodeNumber);
309: // find out if this folder is open or closed
310: Boolean isOpenObject = Boolean.TRUE;
311: boolean isOpen;
312:
313: // if the folder state is still not set, default
314: if (isOpenObject == null) {
315: isOpen = defaultOpen;
316: } else {
317: // use the last setting from the user
318: isOpen = isOpenObject.booleanValue();
319: }
320: // if this node has children and is open and is not leaf, then we
321: // have to parse the children first so we can set the property for
322: // the children
323: if ((model.getChildCount(node) > 0) && isOpen
324: && !model.isLeaf(node)) {
325: nodeProperties.setProperty(CHILDREN_PROPERTY,
326: createChildren(node, level + 1));
327: }
328: // now we just use the renderer and return whatever that gives us
329: try {
330: returnString += renderer.render(model, node, isOpen,
331: level, (nodeNumber == (totalNodes - 1)),
332: getTheme(), nodeProperties);
333: } catch (JspException e) {
334: logger.error("createChildren(Object, int)", e);
335:
336: // catch it to throw it again :-)
337: throw e;
338: } catch (Exception e) {
339: logger.error("createChildren(Object, int)", e);
340:
341: throw new JspException(e);
342: }
343: }
344:
345: if (logger.isDebugEnabled()) {
346: logger
347: .debug("createChildren(Object, int) - end - return value = "
348: + returnString);
349: }
350: return returnString;
351: }
352:
353: /**
354: * <p>This method is called when the JSP engine encounters the start
355: * tag,
356: * after the attributes are processed.<p>
357: *
358: * <p>Scripting variables (if any) have their values set here.</p>
359: *
360: * @return <code>SKIP_BODY</code> if this tag has no body or it
361: * should be skipped, otherwise <code>EVAL_BODY_BUFFERED</code>
362: * @throws JspException if there is an error retrieving the
363: * navigation
364: * object.
365: * @throws JspException if there is no settings object in the
366: * session.
367: * @throws JspException if there is an error wrting to
368: * <code>out.print(
369: * )</code>
370: */
371: public int doStartTag() throws JspException {
372: if (logger.isDebugEnabled()) {
373: logger.debug("doStartTag() - start");
374: }
375:
376: super .doStartTag();
377: try {
378: // now do we have a valid renderer specified? if not, use the
379: // default
380: if (renderer == null) {
381: renderer = new DefaultTreeNodeRenderer();
382: }
383: renderer.setTreeTag(this );
384: JspWriter out = pageContext.getOut();
385:
386: renderer.initialize(pageContext.getSession(),
387: (javax.servlet.http.HttpServletRequest) pageContext
388: .getRequest(), out, pageContext);
389: // create the full tree ( null parent )
390: out.println(createChildren(model.getRoot(), 0));
391: renderer.finalize(pageContext.getSession(),
392: (javax.servlet.http.HttpServletRequest) pageContext
393: .getRequest(), out);
394: } catch (IOException ioException) {
395: logger.error("doStartTag()", ioException);
396:
397: throw new JspException(
398: "Error in TreeTag: IOException whilst printing select: "
399: + ioException.getMessage(), ioException);
400: } catch (Exception e) {
401: logger.error("doStartTag()", e);
402:
403: throw new JspException(e);
404: }
405: // this tag has no body
406:
407: if (logger.isDebugEnabled()) {
408: logger.debug("doStartTag() - end - return value = "
409: + SKIP_BODY);
410: }
411: return SKIP_BODY;
412: }
413:
414: /**
415: * <p>Specifies the id of a folder you wish to close.</p>
416: *
417: * @return the current value of the folder which will be closed, or
418: * <code>null</code> if no folder will be closed.
419: */
420: public final Integer getCloseFolder() {
421: if (logger.isDebugEnabled()) {
422: logger.debug("getCloseFolder() - start");
423: }
424:
425: if (logger.isDebugEnabled()) {
426: logger.debug("getCloseFolder() - end - return value = "
427: + closeFolder);
428: }
429: return closeFolder;
430: }
431:
432: /**
433: * <p>Decides whether folders should be open or closed by default.</p>
434: *
435: * @return <code>true</code> if folders should be opened by default,
436: * otherwise <code>false</code>.
437: */
438: public final boolean getDefaultOpen() {
439: if (logger.isDebugEnabled()) {
440: logger.debug("getDefaultOpen() - start");
441: }
442:
443: if (logger.isDebugEnabled()) {
444: logger.debug("getDefaultOpen() - end - return value = "
445: + defaultOpen);
446: }
447: return defaultOpen;
448: }
449:
450: /**
451: * <p>Get the name of form which will submit when you open or close
452: * folder.</p>
453: *
454: * @return <code>String</code> name of form
455: */
456: public final String getFormName() {
457: if (logger.isDebugEnabled()) {
458: logger.debug("getFormName() - start");
459: }
460:
461: if (logger.isDebugEnabled()) {
462: logger.debug("getFormName() - end - return value = "
463: + this .formName);
464: }
465: return this .formName;
466: }
467:
468: /**
469: * <p>Get the value supplied to the attribute 'model'.</p>
470: *
471: * <p>This model contains the data source for the tree. To use any
472: * datasource
473: * with this tree control, you should first create a class which
474: * implements
475: * {@link javax.swing.tree,TreeModel TreeModel}.</p>
476: *
477: * @return the value supplied to the tag attribute 'model'.
478: *
479: */
480: public final TreeModel getModel() {
481: if (logger.isDebugEnabled()) {
482: logger.debug("getModel() - start");
483: }
484:
485: if (logger.isDebugEnabled()) {
486: logger.debug("getModel() - end - return value = " + model);
487: }
488: return model;
489: }
490:
491: /**
492: * <p>Specifies the id of a folder you wish to open.</p>
493: *
494: * @return the current value of the folder which will be opened, or
495: * <code>null</code> if no folder will be opened.
496: */
497: public final Integer getOpenFolder() {
498: if (logger.isDebugEnabled()) {
499: logger.debug("getOpenFolder() - start");
500: }
501:
502: if (logger.isDebugEnabled()) {
503: logger.debug("getOpenFolder() - end - return value = "
504: + openFolder);
505: }
506: return openFolder;
507: }
508:
509: /**
510: * <p>Get the value supplied to the attribute 'renderer'.</p>
511: *
512: * <p>This object controls the appearance of each node in the tree,
513: * usually
514: * by
515: * parsing sections from the {@link
516: * com.ivata.groupware.web.theme.Theme
517: * Theme}.<br/>
518: * If you do not use this attribute, an instance of {@link
519: * com.ivata.groupware.web.DefaultTreeNodeRenderer
520: * DefaultTreeNodeRenderer} is created and applied.</p>
521: *
522: * @return the value supplied to the tag attribute 'renderer'.
523: *
524: */
525: public final TreeNodeRenderer getRenderer() {
526: if (logger.isDebugEnabled()) {
527: logger.debug("getRenderer() - start");
528: }
529:
530: if (logger.isDebugEnabled()) {
531: logger.debug("getRenderer() - end - return value = "
532: + renderer);
533: }
534: return renderer;
535: }
536:
537: /**
538: * <p>Get the value supplied to the attribute 'treeName'.</p>
539: *
540: * <p>This attribute specifies a unique identifier for this tree,
541: * which is
542: * used to store the state of each foler (open/closed).</p>
543: *
544: * @return the value supplied to the tag attribute 'treeName'.
545: *
546: */
547: public final String getTreeName() {
548: if (logger.isDebugEnabled()) {
549: logger.debug("getTreeName() - start");
550: }
551:
552: if (logger.isDebugEnabled()) {
553: logger.debug("getTreeName() - end - return value = "
554: + treeName);
555: }
556: return treeName;
557: }
558:
559: /**
560: * <p>Specifies the id of a folder you wish to close.</p>
561: *
562: * @param closeFolderParam the new value of the folder you wish to close.
563: * Not setting or setting to <code>null</code> results in no folder being
564: * closed.
565: */
566: public final void setCloseFolder(final Integer closeFolderParam) {
567: if (logger.isDebugEnabled()) {
568: logger.debug("setCloseFolder(Integer closeFolder = "
569: + closeFolderParam + ") - start");
570: }
571:
572: this .closeFolder = closeFolderParam;
573:
574: if (logger.isDebugEnabled()) {
575: logger.debug("setCloseFolder(Integer) - end");
576: }
577: }
578:
579: /**
580: * <p>Decides whether folders should be open or closed by default.</p>
581: *
582: * @param defaultOpenParam set to <code>true</code> if folders should be
583: * opened by default, otherwise <code>false</code>.
584: */
585: public final void setDefaultOpen(final boolean defaultOpenParam) {
586: if (logger.isDebugEnabled()) {
587: logger.debug("setDefaultOpen(boolean defaultOpen = "
588: + defaultOpenParam + ") - start");
589: }
590:
591: this .defaultOpen = defaultOpenParam;
592:
593: if (logger.isDebugEnabled()) {
594: logger.debug("setDefaultOpen(boolean) - end");
595: }
596: }
597:
598: /**
599: * <p>Set the name of form submit when you open or close a folder.</p>
600: *
601: * @param formNameParam The name of form submit when you open or close a
602: * folder.
603: */
604: public final void setFormName(final String formNameParam) {
605: if (logger.isDebugEnabled()) {
606: logger.debug("setFormName(String formName = "
607: + formNameParam + ") - start");
608: }
609:
610: this .formName = formNameParam;
611:
612: if (logger.isDebugEnabled()) {
613: logger.debug("setFormName(String) - end");
614: }
615: }
616:
617: /**
618: * <p>Set the value supplied to the attribute 'model'.</p>
619: *
620: * <p>This model contains the data source for the tree. To use any
621: * datasource
622: * with this tree control, you should first create a class which
623: * implements
624: * {@link javax.swing.tree,TreeModel TreeModel}.</p>
625: *
626: * @param modelParam the new value supplied to the tag attribute 'model'.
627: *
628: */
629: public final void setModel(final TreeModel modelParam) {
630: if (logger.isDebugEnabled()) {
631: logger.debug("setModel(TreeModel model = " + modelParam
632: + ") - start");
633: }
634:
635: this .model = modelParam;
636:
637: if (logger.isDebugEnabled()) {
638: logger.debug("setModel(TreeModel) - end");
639: }
640: }
641:
642: /**
643: * <p>Specifies the id of a folder you wish to open.</p>
644: *
645: * @param openFolderParam the new value of the folder you wish to open. Not
646: * setting
647: * or setting to <code>null</code> results in no folder being opened.
648: */
649: public final void setOpenFolder(final Integer openFolderParam) {
650: if (logger.isDebugEnabled()) {
651: logger.debug("setOpenFolder(Integer openFolder = "
652: + openFolderParam + ") - start");
653: }
654:
655: this .openFolder = openFolderParam;
656:
657: if (logger.isDebugEnabled()) {
658: logger.debug("setOpenFolder(Integer) - end");
659: }
660: }
661:
662: /**
663: * <p>Set the value supplied to the attribute 'renderer'.</p>
664: *
665: * <p>This object controls the appearance of each node in the tree,
666: * usually
667: * by
668: * parsing sections from the {@link
669: * com.ivata.groupware.web.theme.Theme
670: * Theme}.<br/>
671: * If you do not use this attribute, an instance of {@link
672: * com.ivata.groupware.web.DefaultTreeNodeRenderer
673: * DefaultTreeNodeRenderer} is created and applied.</p>
674: *
675: * @param rendererParam the new value supplied to the tag attribute
676: * 'renderer'.
677: *
678: */
679: public final void setRenderer(final TreeNodeRenderer rendererParam) {
680: if (logger.isDebugEnabled()) {
681: logger.debug("setRenderer(TreeNodeRenderer renderer = "
682: + rendererParam + ") - start");
683: }
684:
685: this .renderer = rendererParam;
686:
687: if (logger.isDebugEnabled()) {
688: logger.debug("setRenderer(TreeNodeRenderer) - end");
689: }
690: }
691:
692: /**
693: * <p>Set the value supplied to the attribute 'treeName'.</p>
694: *
695: * <p>This attribute specifies a unique identifier for this tree,
696: * which is
697: * used to store the state of each foler (open/closed).</p>
698: *
699: * @param treeNameParam the new value supplied to the tag attribute
700: * 'treeName'.
701: *
702: */
703: public final void setTreeName(final String treeNameParam) {
704: if (logger.isDebugEnabled()) {
705: logger.debug("setTreeName(String treeName = "
706: + treeNameParam + ") - start");
707: }
708:
709: this .treeName = treeNameParam;
710:
711: if (logger.isDebugEnabled()) {
712: logger.debug("setTreeName(String) - end");
713: }
714: }
715: }
|