用dom4j实现openfire式导航菜单

 

 借鉴Openfire项目中菜单设计的优点,结合我们自己的需求,通过一个小实例,来说明一下如何使用dom4j来实现这个功能。
    可以在http://www.igniterealtime.org/downloads/index.jsp 这里下载到Openfire的发布版和源码版,Openfire的介绍在这里不作描述,网上相关的资料有很多,有兴趣的朋友可以下载源码研究一下,其实不了解Openfire也没关系,因为我们只是借鉴其中的一些思想,等做完了实例,就会有一个比较直观的认识。
    大象建议先在最后面下载源码和必要的JAR包,让程序运行起来看下效果,再看下面的详细说明。
    开发环境:Eclipse 3.2.1  MyEclipse 5.10GA  Tomcat 6.10  
             dom4j-1.6.1.jar  jaxen-1.1-beta-7.jar  sitemesh-2.2.1.jar

    1、创建tag-console.xml
       在src目录下建一个tag-console.xml文件,这个配置文件中写的是菜单信息,内容如下:

<?xml version="1.0" encoding="GBK"?>
<bookstore>
    
<global>
        
<appname>菜单导航demo</appname>
        
<version>ver 1.0</version>
        
<creator>菠萝大象</creator>
    
</global>
    
<catalog id="catalog-program" name="编程开发" url="index.jsp" description="编程开发">     
        
<item id="item-program-java" name="Java开发" url="index.jsp" description="Java开发">

            
<book id="695043" name="Struts2 深入详解" url="index.jsp" description="Struts2 深入详解"/>
            
<book id="691254" name="Ant整合开发" url="book_ant.jsp" description="Ant整合开发"/>
            
<book id="693668" name="Java编程思想" url="book_java.jsp" description="Java编程思想"/>
        
</item>
        
<item id="item-program-database" name="数据库开发" url="book_oracle9.jsp" description="数据库开发">
            
<book id="691245" name="Oracle 9i数据库宝典" url="book_oracle9.jsp" description="Oracle 9i数据库宝典"/>
            
<book id="693254" name="SQL Server 2005应用开发" url="book_sqlserver.jsp" description="SQL Server 2005应用开发"/>
            
<book id="690215" name="Oracle 10g高级开发" url="book_oracle10.jsp" description="Oracle 10g高级开发"/>
        
</item>
    
</catalog>
    
<catalog id="catalog-system" name="系统相关" url="book_vista.jsp" description="系统相关">
        
<item id="item-system-windows" name="Windows系统" url="book_vista.jsp" description="Windows系统">
            
<book id="691258" name="Windows Vista入门" url="book_vista.jsp" description="Windows Vista入门"/>
            
<book id="695489" name="Windows注册表实战" url="book_windows.jsp" description="Windows注册表实战"/>
        
</item>
        
<item id="item-system-linux" name="Linux系统" url="book_linux9.jsp" description="Linux系统">
            
<book id="696598" name="Linux 9.0详解" url="book_linux9.jsp" description="Linux 9.0详解"/>
            
<book id="694585" name="Linux宝典" url="book_linux.jsp" description="Linux宝典"/>
        
</item>
    
</catalog>
</bookstore>

       上面XML里面的东西我是随便写的,大家千万不要较真,我用图书来做菜单一是方便大家理解,另一个是简化程序,其实Openfire的服务器端是一个后台管理系统,它是基于XMPP(可扩展消息处理现场协议)开发的,XMPP贯穿整个系统设计,如果你想用它的控制台框架,但又不想用XMPP,请先从网页入口开始,结合页面仔细分析代码,把需要的部分抽取出来就行了,其它的不用去管。大象没有研究过XMPP,只是抽取了控制台框架,对Openfire的源代码也没能深入的研究,最主要还是E文太烂了。^_^
       Openfire没有采用现在很流行的技术架构(SSH),只使用JSP+JavaBean,但是它有自己的系统设计,就连日志都是自己做的,没有使用我们熟悉的log4j,真的是太佩服鸟~~~~
    
2、创建ResourceManage.java
       在util包下创建ResourceManage类,这个类主要是用来读取tag-console.xml文件,并取得文件中的基本信息,以及查找元素等操作。
       我们先在Constant接口中,增加一个字符串常量:String TAG_CONFIG = "tag-console.xml"
       ResourceManage前面加载资源的部分和上一篇,后来修改过的DataBaseConnect类一样,只需把Constant.DB_CONFIG换成Constant.TAG_CONFIG就行了。
接下来,在类中加入几个读取XML中基本信息的方法:

    <global>
        
<appname>菜单导航demo</appname>
        
<version>ver 1.0</version>
        
<creator>菠萝大象</creator>
    
</global>

       这里只举出取得appname元素值的方法,其它的几个都很相似,请查看源代码。

    /**
     * 得到应用程序名称
     
*/
    
public static  String getAppName(){
        Element appName 
= (Element) coreModel.selectSingleNode("//bookstore/global/appname"
);
        
if(appName!=null
){
            
return
 appName.getText();
        }
else
{
            
return null
;
        }
    }

       根据id属性值查找对应的元素:

    /**
     * 在整个文档节点中查找id属性值为传入id的元素对象
     * 
@param  id 待查找的id属性值
     * 
@return
 返回找到的元素对象
     
*/

    
public static  Element getSingleElementById(String id){
        
return (Element)coreModel.selectSingleNode("//*[@id='"+id+"']"
);
    }

       这里用到了XPATH语法,根据传入的id值,在整个文档中查找id属性值与此一致的元素对象。用下面的代码举例说明:

    <book id="695043" name="Struts2 深入详解" url="index.jsp" description="Struts2 深入详解"/>

       当传入的id属性值为"695043"时,那么我们就会得到对应这个id值的book元素对象,id属性值在整个配置文件中就是一个key关键字,起到定位的作用。
       根据id属性值查找上下文中对应的catalog元素:

    /**
     * 根据传入的id查找上下文中对应的catalog元素
     * 
@param  id 待查找的id属性值
     * 
@return
 返回id值所在的catalog元素对象
     
*/

    
public static  Element getElementByID(String id) {
        
return (Element) coreModel.selectSingleNode("//*[@id='" + id
                + "']/ancestor::catalog");

    }

       ancestorXPATH语法中轴的概念,我引用网上官方文档中的说明:“ancestor(axis)包含上下节点的祖先节点,该祖先节点由其上下文节点的父节点以及父节点的父节点等等诸如此类的节点构成,所ancestor轴总是包含有根节点,除非上下文节点就是根节点本身。”这句话的意思其实就是向上查找节点,直到找到根节点为止。对于ancestor::catalog来说,就是向上查找直到catalog节点为止。所以getElementByID这个方法是根据传入的id属性值在上下文中查找节点,直到找到这个id值所在的上下文catalog节点为止。当传入的id属性值为"695043"时,我们会得到id="catalog-program"这个catalog节点元素,而不会得到id="catalog-system"这个catalog节点元素。这样说大家大概能明白是什么意思了吧?
       可以去这个网站看下XPATH教程:http://www.zvon.org/xxl/XPathTutorial/General_chi/examples.html
    3、自定义标签  
       采用自定义标签的方式来生成菜单,借助ResourceManage类取出XML文件中的信息,将这些内容装载到标签体中,然后在JSP页面中呈现出来。
       1主菜单标签
          主菜单有两个,catalog元素这一层表示主菜单。标签的实现类如下:
          MainTag.java
          建com.demo.tag包,在tag包下创建MainTag类,继承javax.servlet.jsp.tagext.BodyTagSupport
类,主要的部分代码如下,完整代码请下载源码包查看。
          这些属性与demo.tld中的属性对应,每个属性都有settergetter方法。

    private String css; //菜单的CSS样式
    private String currentcss; //当前选中菜单的CSS样式

         doStartTag()方法是遇到标签开始时调用的方法,EVAL_BODY_BUFFERED表示创建一个缓冲流,将标签体的内容保存到BodyContent对象中,可以对其内容进行修改。BodyContent继承了javax.servlet.jsp.JspWriter类,BodyContent对象的内容不自动写入servlet的输出流,而是放在一个字符流缓存中。当标签体完成后其对象仍可在doEndTag()方法中应用,由getString()getReader()方法操作。并在必要时修改及写入恢复的JspWriter输出流。EVAL_BODY_INCLUDE表示将显示标签间的文字。另一个返回值是SKIP_BODY,它表示不显示标签间的文字。

    public int doStartTag() throws  JspException {
        
return EVAL_BODY_BUFFERED; //创建保存到BodyContent对象中的缓冲流

    }

          doEndTag()方法是遇到标签结束时调用的方法,EVAL_PAGE表示处理完标签后继续执行标签之后的JSP页面。另一个返回值SKIP_PAGE表示不处理标签之后的JSP网页。

    public int doEndTag() throws  JspException {
        
//代码主体省略,请查看源码

        return EVAL_PAGE; //处理完标签后继续执行标签之后的JSP页面
    }

          doEndTag()方法中部分比较重要的代码说明:
          使用pageContext对象在JSP页面上下文取得请求,不过请注意pageContext,它定义在javax.servlet.jsp.tagext.TagSupport中,而不是在BodyTagSupport中,因为BodyTagSupport继承了
TagSupport

    //使用pageContext对象在JSP页面上下文取得请求
    HttpServletRequest request = (HttpServletRequest)pageContext.getRequest();

          取得请求中的pageID值,这个pageID值在每个jsp页面中放在meta标签中,通过sitemesh装饰器取出放到request中。

    /*
     * 从请求中得到pageID值,即每个JSP里meta的content值
     * 与XML文件中book元素的id属性值一致
     
*/
    String pageID 
= (String)request.getAttribute("pageID");

          将所有的catalog元素取出放到List集合中,这里是只取得catalog这一层级的元素,实质就是catalogs中只有两个对象,一个是id="catalog-program"另一个是id="catalog-system",使用dom4j我们会发现处理元素非常容易,API相当的丰富,想写成什么样完全凭你自己的想法。

    //将所有的catalog元素取出放到List集合
    List catalogs = ResourceManage.getCoreModel().selectNodes("//catalog");

          看看前面介绍的getElementByID这个方法,这个currentCatalog所表示就是pageID所在的catalog元素。上面的代码是为了和下面的代码结合来判断当前的菜单是否为选中,加入CSS样式显示。

    //pageID所在的catalog元素,主要用来判断当前菜单是否被选中
    Element currentCatalog = (Element)ResourceManage.getElementByID(pageID);

          从BodyContent中将标签体缓存流读取出来,标签在WebRoot/decorators/main.jsp中:<a href="[url]" title="[description]" onmouseover="self.status='[description]';return true;" onmouseout="self.status='';return true;">[name]</a>

    String value = getBodyContent().getString(); //得到标签体

          Catalogs里面是两个catalog对象,循环遍历取出,将标签体中的[id][url][name][description]替换为XML文件中的属性值,这样主菜单标签就生成了。

    for (int i=0; i<catalogs.size(); i++ ) {
        Element catalog 
= (Element)catalogs.get(i); //catalog元素对象

        String value = getBodyContent().getString(); //得到标签体
        /*
         * 将标签体中的[id]、[url]、[name]、[description]
         * 替换为XML文件中的属性值
         * attributeValue方法是取属性值
         
*/
        
if (value != null ) {
            value 
= StringUtils.replace(value, "[url]"
, request
                       .getContextPath()
                       
+ "/" + catalog.attributeValue("url"
));            
            value 
= StringUtils.replace(value, "[name]",catalog.attributeValue("name"
));
            value 
= StringUtils.replace(value, "[description]"
,catalog.
                    attributeValue(
"description"
));
        }
        String css 
=
 getCss();
        
//对当前选中菜单添加CSS样式

        if  (catalog.equals(currentCatalog)) {
            css 
=
 getCurrentcss();
        }
        buf.append(
"<li class=\"").append(css).append("\">"
);
        
if (i > 0
) {
            buf.append(
" | "
);
        }
        buf.append(value).append(
"</li>"
);
    }

       2、导航菜单及侧边栏菜单标签
           导航菜单每个catalog下都有两个,而侧边栏菜单则在item下定义,这两个标签类与主菜单的标签类没有太大的区别,主要就是生成标签体,匹配CSS样式,因此,代码中相同的部分我不再细述,只说一下不同的地方。
           在tag包下创建NavTag类和SideTag类,标签属性与MainTag一样,只是SideTag多了一个headercss属性,这是在页面显示时,加在边栏上当前选中项左侧小箭头的CSS
样式,不清楚的话,请运行程序后观察。
           NavTag.java
           根据pageID找到此元素对象:

    //根据pageID找到此元素对象,即book元素对象
    Element current = ResourceManage.getSingleElementById(pageID);

           如果current不为空,取得父节点,其为item元素。根据pageID值,如果为695043,则subnavid="item-program-java"item元素,如果为691245,则subnavid="item-program-database"item元素。这个subnav的作用也是用来判断当前的菜单是否为选中,加入CSS样式显示。

    Element subnav = null ;
    
if (current != null
) {
        subnav 
= current.getParent(); //取得父节点,即item元素

    }

           SideTag.java
           在SideTag中也有上面的代码,但是subnav不再与CSS有关,而是取得它的所有子元素集合,即book元素集合,然后遍历所有book节点,取出属性值放入标签体中再输出到页面。
           我注释写得很详细,请查看代码了解细节。
    
4、创建StringUtils.java
       在util包下创建StringUtils类,这个类作为字符串处理类。添加public static String replace(String string, String oldString, String newString)方法,它的作用就是将标签体中的[id][url][name][description]替换为XML文件中的属性值。如果被替换的字符串在标签体中有多个,也能将它全部替换。

    /**
     * 将string中的oldString全部替换为newString
     * 
@param  string 原始字符串
     * 
@param
 oldString 被替换的字符串
     * 
@param
 newString 要替换的字符串
     * 
@return
 返回替换完后的新string
     
*/

    
public static  String replace(String string, String oldString, String newString) {
        
if (string == null
) {
            
return null
;
        }
        
int i = 0
;
        
//判断string中是否有被替换的字符串,i其实是索引值

        if ((i = string.indexOf(oldString, i)) >= 0 ) {
            
char[] string2 = string.toCharArray(); //字符串放入数组

            char[] newString2 = newString.toCharArray(); //要替换的字符串
            int oLength = oldString.length(); //被替换的字符串的长度
            StringBuilder buf = new  StringBuilder(string2.length);
            
/*

             * 从索引0开始,按i值的长度在string2数组中截取字符
             * 将截取的字符放到buf中,接着再加入要替换的内容
             
*/
            buf.append(string2, 
0 , i).append(newString2);
            i 
+=
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值