/*
* Copyright (c) 2001 - 2005 ivata limited.
* All rights reserved.
* -----------------------------------------------------------------------------
* ivata groupware may be redistributed under the GNU General Public
* License as published by the Free Software Foundation;
* version 2 of the License.
*
* These programs are free software; you can redistribute them and/or
* modify them under the terms of the GNU General Public License
* as published by the Free Software Foundation; version 2 of the License.
*
* These programs are distributed in the hope that they will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See the GNU General Public License in the file LICENSE.txt for more
* details.
*
* If you would like a copy of the GNU General Public License write to
*
* Free Software Foundation, Inc.
* 59 Temple Place - Suite 330
* Boston, MA 02111-1307, USA.
*
*
* To arrange commercial support and licensing, contact ivata at
* http://www.ivata.com/contact.jsp
* -----------------------------------------------------------------------------
* $Log: TreeTag.java,v $
* Revision 1.3 2005/10/11 18:51:38 colinmacleod
* Fixed some checkstyle and javadoc issues.
*
* Revision 1.2 2005/10/02 14:08:57 colinmacleod
* Added/improved log4j logging.
*
* Revision 1.1 2005/09/29 13:44:03 colinmacleod
* Moved to core subproject.
*
* Revision 1.4 2005/09/14 14:51:43 colinmacleod
* Removed unused local and class variables.
* Added serialVersionUID.
*
* Revision 1.3 2005/04/10 18:47:36 colinmacleod
* Changed i tag to em and b tag to strong.
*
* Revision 1.2 2005/04/09 17:19:10 colinmacleod
* Changed copyright text to GPL v2 explicitly.
*
* Revision 1.1.1.1 2005/03/10 17:50:36 colinmacleod
* Restructured ivata op around Hibernate/PicoContainer.
* Renamed ivata groupware.
*
* Revision 1.2 2004/11/03 16:10:12 colinmacleod
* Changed todo comments to TODO: all caps.
*
* Revision 1.1 2004/09/30 15:16:03 colinmacleod
* Split off addressbook elements into security subproject.
*
* Revision 1.3 2004/07/13 19:41:15 colinmacleod
* Moved project to POJOs from EJBs.
* Applied PicoContainer to services layer (replacing session EJBs).
* Applied Hibernate to persistence layer (replacing entity EJBs).
*
* Revision 1.2 2004/03/21 21:16:18 colinmacleod
* Shortened name to ivata op.
*
* Revision 1.1.1.1 2004/01/27 20:57:58 colinmacleod
* Moved ivata openportal to SourceForge..
*
* Revision 1.2 2003/10/17 12:36:13 jano
* fixing problems with building
* converting intranet -> portal
* Eclipse building
*
* Revision 1.1.1.1 2003/10/13 20:50:14 colin
* Restructured portal into subprojects
*
* Revision 1.1 2003/02/24 19:33:33 colin
* moved to jsp
*
* Revision 1.10 2003/02/04 17:43:51 colin
* copyright notice
*
* Revision 1.9 2003/02/01 12:48:11 colin
* improved error handling
*
* Revision 1.8 2003/01/28 12:57:59 colin
* made TreeTag a subclass of ControlTag (rather than WebGuiTag)
*
* Revision 1.7 2003/01/24 19:31:19 peter
* the renderer initialise method was changed...
*
* Revision 1.6 2002/09/16 14:34:20 jano
* added new field formName
*
* Revision 1.5 2002/08/28 12:54:12 jano
* method createChildren changed
*
* Revision 1.4 2002/08/11 11:57:12 colin
* Structural changes to make the design more flexible, for implementing
* comment trees.
*
* Revision 1.3 2002/06/21 12:11:13 colin
* restructured com.ivata.groupware.web and split into separate
* subcategories
*
* Revision 1.2 2002/06/13 15:45:15 peter
* brought over to peter, fixed bugs in webgui property-settings
*
* Revision 1.1 2002/06/13 07:44:07 colin
* first version of rose model: code tidied up/added javadoc
*
* Revision 1.5 2002/02/03 15:24:08 colin
* changed classname for User to PersonUser
*
* Revision 1.4 2002/02/02 21:23:01 colin
* major restructuring to make the Settings class more generic
* all default settings are now taken from the database rather than
* being hard coded in the settings class
* settings are stored in a HashMap in settings
*
* Revision 1.3 2002/01/27 19:55:48 colin
* updated the themes by removing the multiple section tags and
* replacing them with one tag and a Properties instance in
* com.ivata.groupware.web.theme.Theme
*
* Revision 1.2 2002/01/24 13:19:40 colin
* consolidated hanlding of theme and properties tags acoss webgui tag
* library
*
* Revision 1.1 2002/01/20 19:28:25 colin
* added tab and tree tags
* implemented address book functionality
* -----------------------------------------------------------------------------
*/
package com.ivata.groupware.web.tag.webgui.tree;
import java.io.IOException;
import java.util.Properties;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.swing.tree.TreeModel;
import org.apache.log4j.Logger;
import com.ivata.groupware.web.tree.DefaultTreeNodeRenderer;
import com.ivata.groupware.web.tree.TreeNode;
import com.ivata.groupware.web.tree.TreeNodeRenderer;
import com.ivata.mask.util.CollectionHandling;
import com.ivata.mask.web.tag.webgui.ControlTag;
/**
* <p>Create a tree from a {@link javax.swing.tree.TreeModel TreeModel}.</p>
* <p>This tree can be displayed is displayed as an HTML table with links on
* each
* node.</p>
* <p><strong>Tag attributes:</strong><br/>
* <table cellpadding='2' cellspacing='5' border='0' align='center'
* width='85%'>
* <tr class='TableHeadingColor'>
* <th>attribute</th>
* <th>reqd.</th>
* <th>param. class</th>
* <th width='100%'>description</th>
* </tr>
* <tr class='TableRowColor'>
* <td>defaultOpen</td>
* <td>true</td>
* <td>boolean</td>
* <td>Set to <code>true</code> if you want tree nodes to be open by
* default. Otherwise they will be closed.</td>
* </tr>
* <tr class='TableRowColor'>
* <td>model</td>
* <td>true</td>
* <td>{@link javax.swing.tree.TreeModel javax.swing.tree.TreeModel}</td>
* <td>This model contains the data source for the tree. To use any
* datasource
* with this tree control, you should first create a class which implements
* {@link javax.swing.tree,TreeModel TreeModel}.</td>
* </tr>
* <tr class='TableRowColor'>
* <td>renderer</td>
* <td>false</td>
* <td>{@link com.ivata.groupware.web.tree.TreeNodeRenderer
* com.ivata.groupware.web.tree.TreeNodeRenderer}</td>
* <td>This object controls the appearance of each node in the tree,
* usually by parsing sections from the {@link
* com.ivata.groupware.web.theme.Theme
* Theme}.<br/>
* If you do not use this attribute, an instance of {@link
* com.ivata.groupware.web.DefaultTreeNodeRenderer
* DefaultTreeNodeRenderer} is created and applied.</td>
* </tr>
* <tr class='TableRowColor'>
* <td>treeName</td>
* <td>true</td>
* <td><code>String</code></td>
* <td>Specifies a unique identifier for this tree, which is used to store
* the state of each foler (open/closed).</td>
* </tr>
* <tr class='TableRowColor'>
* <td>userName</td>
* <td>true</td>
* <td><code>String</code></td>
* <td>Name of the user for whom to draw the tree. The state of each node is
* stored for this user and the appearance in recalled the next time the tree is
* drawn.</td>
* </tr>
* </table>
* </p>
*
* @since 2001-12-15
* @author Colin MacLeod
* <a href='mailto:colin.macleod@ivata.com'>colin.macleod@ivata.com</a>
* @version $Revision: 1.3 $
*/
public class TreeTag extends ControlTag {
/**
* <p>This is the special property used to identify the location
* of the children in the open tag.</p>
*
* <p><strong>Note</strong> that this has to be specified exactly, with
* <u>no spaces</u>.</p>
*/
static final String CHILDREN_PROPERTY = "treeChildren";
/**
* Logger for this class.
*/
private static final Logger logger = Logger.getLogger(TreeTag.class);
/**
* Serialization version (for <code>Serializable</code> interface).
*/
private static final long serialVersionUID = 1L;
/**
* <p>Specifies the id of a folder you wish to open.</p>
*/
private Integer closeFolder = null;
/**
* <p>Decides whether folders should be open or closed by default.</p>
*/
private boolean defaultOpen = false;
/**
* <p>if you set up name of form it will submit this form when you
* open or close folder, otherwise it will make link to same page with
* parameters.</p>
*/
private String formName = null;
/**
* <p>Property declaration for tag attribute: model.</p>
*/
private TreeModel model = null;
/**
* <p>Specifies the id of a folder you wish to open.</p>
*/
private Integer openFolder = null;
/**
* <p>This renderer actually draws each node of the tree. If you do
* not set a
* renderer, then a <code>DefaultTreeNodeRenderer</code> is created
* and
* applied.</p>
*/
private TreeNodeRenderer renderer;
/**
* <p>Property declaration for tag attribute: treeName.</p>
*/
private String treeName = null;
/**
* <p>Default constructor.</p>
*/
public TreeTag() {
super();
}
/**
* <p>This is the method which performs the clever stuff and actually
* creates the tree by recursing on itself, for a table mode
* tree.</p>
*
* @param parent the node for which to display all the children.
* @param level the depth of this node within the tree, with 0 being
* root.
* @return the parsed HTML tree as
* @throws JspException if there is any <code>IOException</code>.
*/
private String createChildren(final Object parent,
final int level) throws JspException {
if (logger.isDebugEnabled()) {
logger.debug("createChildren(Object parent = " + parent
+ ", int level = " + level + ") - start");
}
String returnString = "";
int totalNodes = model.getChildCount(parent);
for (int nodeNumber = 0; nodeNumber < totalNodes; ++nodeNumber) {
// make a fresh copy of the properties for each node
Properties nodeProperties = CollectionHandling.splice(
getProperties(), new Properties());
TreeNode node = (TreeNode) model.getChild(parent, nodeNumber);
// find out if this folder is open or closed
Boolean isOpenObject = Boolean.TRUE;
boolean isOpen;
// if the folder state is still not set, default
if (isOpenObject == null) {
isOpen = defaultOpen;
} else {
// use the last setting from the user
isOpen = isOpenObject.booleanValue();
}
// if this node has children and is open and is not leaf, then we
// have to parse the children first so we can set the property for
// the children
if ((model.getChildCount(node) > 0)
&& isOpen
&& !model.isLeaf(node)) {
nodeProperties.setProperty(CHILDREN_PROPERTY,
createChildren(node,
level + 1));
}
// now we just use the renderer and return whatever that gives us
try {
returnString += renderer.render(model, node, isOpen,
level,
(nodeNumber == (totalNodes - 1)),
getTheme(),
nodeProperties);
} catch (JspException e) {
logger.error("createChildren(Object, int)", e);
// catch it to throw it again :-)
throw e;
} catch (Exception e) {
logger.error("createChildren(Object, int)", e);
throw new JspException(e);
}
}
if (logger.isDebugEnabled()) {
logger.debug("createChildren(Object, int) - end - return value = "
+ returnString);
}
return returnString;
}
/**
* <p>This method is called when the JSP engine encounters the start
* tag,
* after the attributes are processed.<p>
*
* <p>Scripting variables (if any) have their values set here.</p>
*
* @return <code>SKIP_BODY</code> if this tag has no body or it
* should be skipped, otherwise <code>EVAL_BODY_BUFFERED</code>
* @throws JspException if there is an error retrieving the
* navigation
* object.
* @throws JspException if there is no settings object in the
* session.
* @throws JspException if there is an error wrting to
* <code>out.print(
* )</code>
*/
public int doStartTag() throws JspException {
if (logger.isDebugEnabled()) {
logger.debug("doStartTag() - start");
}
super.doStartTag();
try {
// now do we have a valid renderer specified? if not, use the
// default
if (renderer == null) {
renderer = new DefaultTreeNodeRenderer();
}
renderer.setTreeTag(this);
JspWriter out = pageContext.getOut();
renderer.initialize(pageContext.getSession(),
(javax.servlet.http.HttpServletRequest)
pageContext.getRequest(),
out, pageContext);
// create the full tree ( null parent )
out.println(createChildren(model.getRoot(), 0));
renderer.finalize(pageContext.getSession(),
(javax.servlet.http.HttpServletRequest)
pageContext.getRequest(),
out);
} catch (IOException ioException) {
logger.error("doStartTag()", ioException);
throw new JspException(
"Error in TreeTag: IOException whilst printing select: "
+ ioException.getMessage(),
ioException);
} catch (Exception e) {
logger.error("doStartTag()", e);
throw new JspException(e);
}
// this tag has no body
if (logger.isDebugEnabled()) {
logger.debug("doStartTag() - end - return value = " + SKIP_BODY);
}
return SKIP_BODY;
}
/**
* <p>Specifies the id of a folder you wish to close.</p>
*
* @return the current value of the folder which will be closed, or
* <code>null</code> if no folder will be closed.
*/
public final Integer getCloseFolder() {
if (logger.isDebugEnabled()) {
logger.debug("getCloseFolder() - start");
}
if (logger.isDebugEnabled()) {
logger.debug("getCloseFolder() - end - return value = "
+ closeFolder);
}
return closeFolder;
}
/**
* <p>Decides whether folders should be open or closed by default.</p>
*
* @return <code>true</code> if folders should be opened by default,
* otherwise <code>false</code>.
*/
public final boolean getDefaultOpen() {
if (logger.isDebugEnabled()) {
logger.debug("getDefaultOpen() - start");
}
if (logger.isDebugEnabled()) {
logger.debug("getDefaultOpen() - end - return value = "
+ defaultOpen);
}
return defaultOpen;
}
/**
* <p>Get the name of form which will submit when you open or close
* folder.</p>
*
* @return <code>String</code> name of form
*/
public final String getFormName() {
if (logger.isDebugEnabled()) {
logger.debug("getFormName() - start");
}
if (logger.isDebugEnabled()) {
logger.debug("getFormName() - end - return value = "
+ this.formName);
}
return this.formName;
}
/**
* <p>Get the value supplied to the attribute 'model'.</p>
*
* <p>This model contains the data source for the tree. To use any
* datasource
* with this tree control, you should first create a class which
* implements
* {@link javax.swing.tree,TreeModel TreeModel}.</p>
*
* @return the value supplied to the tag attribute 'model'.
*
*/
public final TreeModel getModel() {
if (logger.isDebugEnabled()) {
logger.debug("getModel() - start");
}
if (logger.isDebugEnabled()) {
logger.debug("getModel() - end - return value = " + model);
}
return model;
}
/**
* <p>Specifies the id of a folder you wish to open.</p>
*
* @return the current value of the folder which will be opened, or
* <code>null</code> if no folder will be opened.
*/
public final Integer getOpenFolder() {
if (logger.isDebugEnabled()) {
logger.debug("getOpenFolder() - start");
}
if (logger.isDebugEnabled()) {
logger
.debug("getOpenFolder() - end - return value = "
+ openFolder);
}
return openFolder;
}
/**
* <p>Get the value supplied to the attribute 'renderer'.</p>
*
* <p>This object controls the appearance of each node in the tree,
* usually
* by
* parsing sections from the {@link
* com.ivata.groupware.web.theme.Theme
* Theme}.<br/>
* If you do not use this attribute, an instance of {@link
* com.ivata.groupware.web.DefaultTreeNodeRenderer
* DefaultTreeNodeRenderer} is created and applied.</p>
*
* @return the value supplied to the tag attribute 'renderer'.
*
*/
public final TreeNodeRenderer getRenderer() {
if (logger.isDebugEnabled()) {
logger.debug("getRenderer() - start");
}
if (logger.isDebugEnabled()) {
logger.debug("getRenderer() - end - return value = " + renderer);
}
return renderer;
}
/**
* <p>Get the value supplied to the attribute 'treeName'.</p>
*
* <p>This attribute specifies a unique identifier for this tree,
* which is
* used to store the state of each foler (open/closed).</p>
*
* @return the value supplied to the tag attribute 'treeName'.
*
*/
public final String getTreeName() {
if (logger.isDebugEnabled()) {
logger.debug("getTreeName() - start");
}
if (logger.isDebugEnabled()) {
logger.debug("getTreeName() - end - return value = " + treeName);
}
return treeName;
}
/**
* <p>Specifies the id of a folder you wish to close.</p>
*
* @param closeFolderParam the new value of the folder you wish to close.
* Not setting or setting to <code>null</code> results in no folder being
* closed.
*/
public final void setCloseFolder(final Integer closeFolderParam) {
if (logger.isDebugEnabled()) {
logger.debug("setCloseFolder(Integer closeFolder = "
+ closeFolderParam
+ ") - start");
}
this.closeFolder = closeFolderParam;
if (logger.isDebugEnabled()) {
logger.debug("setCloseFolder(Integer) - end");
}
}
/**
* <p>Decides whether folders should be open or closed by default.</p>
*
* @param defaultOpenParam set to <code>true</code> if folders should be
* opened by default, otherwise <code>false</code>.
*/
public final void setDefaultOpen(final boolean defaultOpenParam) {
if (logger.isDebugEnabled()) {
logger.debug("setDefaultOpen(boolean defaultOpen = "
+ defaultOpenParam
+ ") - start");
}
this.defaultOpen = defaultOpenParam;
if (logger.isDebugEnabled()) {
logger.debug("setDefaultOpen(boolean) - end");
}
}
/**
* <p>Set the name of form submit when you open or close a folder.</p>
*
* @param formNameParam The name of form submit when you open or close a
* folder.
*/
public final void setFormName(final String formNameParam) {
if (logger.isDebugEnabled()) {
logger.debug("setFormName(String formName = " + formNameParam
+ ") - start");
}
this.formName = formNameParam;
if (logger.isDebugEnabled()) {
logger.debug("setFormName(String) - end");
}
}
/**
* <p>Set the value supplied to the attribute 'model'.</p>
*
* <p>This model contains the data source for the tree. To use any
* datasource
* with this tree control, you should first create a class which
* implements
* {@link javax.swing.tree,TreeModel TreeModel}.</p>
*
* @param modelParam the new value supplied to the tag attribute 'model'.
*
*/
public final void setModel(final TreeModel modelParam) {
if (logger.isDebugEnabled()) {
logger.debug("setModel(TreeModel model = "
+ modelParam + ") - start");
}
this.model = modelParam;
if (logger.isDebugEnabled()) {
logger.debug("setModel(TreeModel) - end");
}
}
/**
* <p>Specifies the id of a folder you wish to open.</p>
*
* @param openFolderParam the new value of the folder you wish to open. Not
* setting
* or setting to <code>null</code> results in no folder being opened.
*/
public final void setOpenFolder(final Integer openFolderParam) {
if (logger.isDebugEnabled()) {
logger.debug("setOpenFolder(Integer openFolder = "
+ openFolderParam
+ ") - start");
}
this.openFolder = openFolderParam;
if (logger.isDebugEnabled()) {
logger.debug("setOpenFolder(Integer) - end");
}
}
/**
* <p>Set the value supplied to the attribute 'renderer'.</p>
*
* <p>This object controls the appearance of each node in the tree,
* usually
* by
* parsing sections from the {@link
* com.ivata.groupware.web.theme.Theme
* Theme}.<br/>
* If you do not use this attribute, an instance of {@link
* com.ivata.groupware.web.DefaultTreeNodeRenderer
* DefaultTreeNodeRenderer} is created and applied.</p>
*
* @param rendererParam the new value supplied to the tag attribute
* 'renderer'.
*
*/
public final void setRenderer(final TreeNodeRenderer rendererParam) {
if (logger.isDebugEnabled()) {
logger.debug("setRenderer(TreeNodeRenderer renderer = "
+ rendererParam
+ ") - start");
}
this.renderer = rendererParam;
if (logger.isDebugEnabled()) {
logger.debug("setRenderer(TreeNodeRenderer) - end");
}
}
/**
* <p>Set the value supplied to the attribute 'treeName'.</p>
*
* <p>This attribute specifies a unique identifier for this tree,
* which is
* used to store the state of each foler (open/closed).</p>
*
* @param treeNameParam the new value supplied to the tag attribute
* 'treeName'.
*
*/
public final void setTreeName(final String treeNameParam) {
if (logger.isDebugEnabled()) {
logger.debug("setTreeName(String treeName = " + treeNameParam
+ ") - start");
}
this.treeName = treeNameParam;
if (logger.isDebugEnabled()) {
logger.debug("setTreeName(String) - end");
}
}
}
|