闲话少说,如果您也在使用 Tomcat 5.5.26 Administration Tool 的 Delete Existing Hosts 功能时遇到如下问题,本文至少能提供一种有效的解决方法(贴上错误信息文本,一是给搜索引擎预备的,二来也帮助您确认一下本文是否对症)。

HTTP Status 500 –

——————————————————————————–

type Exception report

message

description The server encountered an internal error () that prevented it from fulfilling this request.

exception

java.lang.NullPointerException

    java.lang.String.indexOf(Unknown Source)

    java.lang.String.indexOf(Unknown Source)

    org.apache.struts.taglib.logic.MatchTag.condition(MatchTag.java:158)

    org.apache.struts.taglib.logic.MatchTag.condition(MatchTag.java:100)

    org.apache.struts.taglib.logic.ConditionalTagBase.doStartTag(ConditionalTagBase.java:174)

    admin.host.hosts_jsp._jspService(hosts_jsp.java:178)

    org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:98)

    javax.servlet.http.HttpServlet.service(HttpServlet.java:803)

    org.apache.struts.action.RequestProcessor.doForward(RequestProcessor.java:1085)

    org.apache.struts.action.RequestProcessor.processForwardConfig(RequestProcessor.java:398)

    org.apache.struts.action.RequestProcessor.process(RequestProcessor.java:241)

    org.apache.struts.action.ActionServlet.process(ActionServlet.java:1196)

    org.apache.struts.action.ActionServlet.doGet(ActionServlet.java:414)

    javax.servlet.http.HttpServlet.service(HttpServlet.java:690)

    javax.servlet.http.HttpServlet.service(HttpServlet.java:803)

    org.apache.webapp.admin.filters.SetCharacterEncodingFilter.doFilter(SetCharacterEncodingFilter.java:123)

note The full stack trace of the root cause is available in the Apache Tomcat/5.5.26 logs.

——————————————————————————–

Apache Tomcat/5.5.26

如果您只想解决问题,也就是顺利使用 Delete Exsiting Hosts 功能,那么您只需:

  1. 浏览 http://tomcat.apache.org/download-55.cgi,在 Source Code Distributions 下载源代码压缩包(条件允许的话,最好借助 md5 等方式检查一下文件完整性);
  2. 解压缩下载的源代码压缩包,找到 apache-tomcat-5.5.26-srccontainerwebappsadminWEB-INFclassesorgapachewebappadminhostDeleteHostAction.java 的第 116 行,将 “domain” 两侧的一对双引号删除;
  3. 使用您熟悉的方式,重新编译该文件,获得 DeleteHostAction.class 或者 jar 文件(我编译好的 DeleteHostAction.class 可以在 CSDN 下载频道 获取);
  4. 将编译好的文件部署到服务器上的正确位置,%CATALINA_HOME%serverwebappsadminWEB-INFclassesorgapachewebappadminhostDeleteHostAction.class 或者 %CATALINA_HOME%serverwebappsadminWEB-INFlibcatalina-admin.jar,二者必取其一。我采取的方法是重命名 %CATALINA_HOME%serverwebappsadminWEB-INFlibcatalina-admin.jar 把她屏蔽掉,然后把其内容解压缩到 %CATALINA_HOME%serverwebappsadminWEB-INFclasses,这样就可以直接拿 DeletaHostAction.class 文件去替换掉原来的那个,一是怕还有别的 .class 需要改,二是图省事儿,就没用 jar 的方式。

下面就是本人发现这一解决方法的始末。事先声明,本人一直走 ASP.NET 路线,对 Java 及相关技术知之甚少,如有疏漏还请指正,互相学习共同提高。

从上述调用栈我们可以看出,问题出在 admin.host.hosts_jsp._jspService 这个方法中,在源文件 hosts_jsp.java 的 178 行,好,那就开始寻找源文件。费了一番周折才发现,原来 Administration Tool 的源代码都在 Tomcat 5.5.26 的 src 包里:apache-tomcat-5.5.26-src.tar.gz 或 .zip(http://tomcat.apache.org/download-55.cgi)。压缩包中的具体位置是 apache-tomcat-5.5.26-srccontainerwebappsadminhosthosts.jsp。

这中间还有一个插曲,我估计是出于性能考虑,官方发布的这个 Admin Web Application 是把 JSP 编译好了当 Servlet 使,因为发现 %CATALINA_HOME%serverwebappsadminWEB-INFweb.xml 中有 配置,把对 JSP 的请求交给编译好的形如 jspname_jsp.class 的 Servlet 来处理。在找到官方发布的源代码之前,我曾经带着学习的目的反编译了那个 admin.host.hosts_jsp.class(位于 %CATALINA_HOME%serverwebappsadminWEB-INFlibcatalina-admin.jar),其内容真是惨不忍睹啊……

我们还是看看正儿八经的源文件吧,hosts.jsp 中有如下一段:

文件片断:apache-tomcat-5.5.26-srccontainerwebappsadminhosthosts.jsp

  •              <logic:match name=”host”                         value='<%= (String)request.getAttribute(“adminAppHost”) %>’>
  •              *              </logic:match>
  •              <logic:notMatch name=”host”                         value='<%= (String)request.getAttribute(“adminAppHost”) %>’>
  •                                       <html:multibox property=”hosts”
  •                                 value=”<%= host.toString() %>” styleId=”hosts”/>               </logic:notMatch> 看到这里我就确信,报异常的就是这个地方:既有 <logic:match /> 和 <logic:nomatch /> 标记(尽管不知道她如何起作用,但语义还是能看出来的),又有两个将某种东西强制转换成 String 的地方(第 78 和 82 行)。

这段源代码和最早遇到的调用栈告诉我,request.getAttribute(“adminAppHost”) 得到了 null。我就很好奇,到底她当初是怎么 setAttribute(“adminAppHost”, value) 的呢?这次就要从页面入手了。登录 Administration Tool,右键右下角的 frame 查看源文件:

代码片断:EditService[1]

  • —–Available Actions—–

只看第 103 行即可,她是说,选择“Delete Existing Hosts”下拉列表项之后,发生了 DeleteHost.do(还带了乱七八糟的参数,%3A:%3D=%2C,),托 %CATALINA_HOME%serverwebappsadminWEB-INFstruts-config.xml 的福,找到了 DeleteHost.do 的责任者:

文件片断:%CATALINA_HOME%serverwebappsadminWEB-INFstruts-config.xml

  •     <!– Set up Delete Hosts transaction –>     <action    path=”/DeleteHost”
  •                type=”org.apache.webapp.admin.host.DeleteHostAction”                name=”hostsForm”
  •                scope=”request”/> 于是去查看 apache-tomcat-5.5.26-srccontainerwebappsadminWEB-INFclassesorgapachewebappadminhostDeleteHostAction.java 的内容。果不其然,这里有 setAttribute:

文件片断:apache-tomcat-5.5.26-srccontainerwebappsadminweb-infclassesorgapachewebappadminhostdeletehostaction.java

  •         // Set up a form bean containing the currently selected         // objects to be deleted
  •         HostsForm hostsForm = new HostsForm();         String select = request.getParameter(“select”);
  •         String domain = null;         if (select != null) {
  •             String hosts[] = new String[1];             hosts[0] = select;
  •             hostsForm.setHosts(hosts);                          
  •             try {                 domain = (new ObjectName(select)).getDomain();
  •             } catch (Exception e) {                 throw new ServletException
  •                 (“Error extracting service name from the host to be deleted”, e);             }        
  •         }         String adminHost = null;
  •         // Get the host name the admin app runs on         // this host cannot be deleted from the admin tool
  •         try {             adminHost = Lists.getAdminAppHost(
  •                                   mBServer, “domain” ,request);         } catch (Exception e) {
  •             String message =                 resources.getMessage(locale, “error.hostName.bad”,
  •                                         adminHost);             getServlet().log(message);
  •             response.sendError(HttpServletResponse.SC_BAD_REQUEST, message);             return (null);
  •         }         request.setAttribute(“adminAppHost”, adminHost);        
  •         request.setAttribute(“hostsForm”, hostsForm); 代码截取的比较多,先看第 98 和 105 行,知道这个作用域内有个局部变量叫 domain 就行了。然后看第 111 和 115 行,还有第 125 行。好了,现在问题集中在了 Lists.getAdminAppHost() 上。不出意外(也就是不抛异常)的话,显然 Lists.getAdminAppHost() 的返回值被 request.setAttribute() 设置成了 adminAppHost 这个 attribute 的取值。

那么我们来看看 Lists 她是怎么说的,找到源文件 apache-tomcat-5.5.26-srccontainerwebappsadminWEB-INFclassesorgapachewebappadminLists.java:

文件片断:apache-tomcat-5.5.26-srccontainerwebappsadminWEB-INFclassesorgapachewebappadminLists.java

  •     /**      * Return the  Host object name string

  •      * that the admin app belongs to.      *

  •      * @param mbserver MBeanServer from which to retrieve the list      * @param request Http request

  •      *      * @exception Exception if thrown while retrieving the list

  •      */     public static String getAdminAppHost

  •         (MBeanServer mbserver, String domain, HttpServletRequest request)         throws Exception {

  •                  // Get the admin app’s host name

  •         StringBuffer sb = new StringBuffer(domain);         sb.append(“:j2eeType=WebModule,*”);  

  •         ObjectName search = new ObjectName(sb.toString());         Iterator names = mbserver.queryNames(search, null).iterator();

  •         String contextPath = request.getContextPath();         String host = null;

  •         String name = null;         ObjectName oname = null;

  •         while (names.hasNext()) {                    name = names.next().toString();

  •             oname = new ObjectName(name);             host = oname.getKeyProperty(“name”);

  •             host = host.substring(2);             int i = host.indexOf(“/”);

  •             if (contextPath.equals(host.substring(i))) {                 host = host.substring(0,i);

  •                 return host;             }

  •         }         return host;

        } 简单来说,getAdminAppHost() 这个方法的第二个参数 domain 被用来寻找 names,而 names 中有我们需要的 host 的内容。经过调试,发现当 domain 取值为 “domain” 时,names.hasNext() 返回 false,host 即最终的返回值亦无可避免成为 null,直至导致 hosts.jsp 中 request.getAttribute(“adminAppHost”) 时得到 null。回过头来看看 DeleteHostAction.java 的第 115 和 116 行吧,第二个参数是常量字符串 “domain” 而不是局部变量 domain,当去掉这一对双引号,再重新编译后,问题看起来就解决了(见下图,逻辑很正确,Admin Tool 所在的 Host 不能删除)。

现在唯一还困扰我的就是,官方发布的版本为什么会存在这个问题?我相信除了遇到相同问题的我们,有更多的人没有遇到这一问题,尤其是在发现 %CATALINA_HOME%serverwebappsadminWEB-INFweb.xml 中的这句话之后,我被彻底打败了……

文件片断:%CATALINA_HOME%serverwebappsadminWEB-INFweb.xml

  •            domain
  •       Catalina