探索WURFL(转自:developerworks)

19 篇文章 0 订阅

多服务简介

普适计算应用程序中的多服务

多服务是一种新兴概念。它使 Internet 应用程序能够为不同类型的请求客户机提供定制响应。

多服务在普适计算应用程序中的使用越来越普遍。当今,大量具有不同功能的无线设备都连接到 Internet。因此,对于很多 Internet 应用程序来说,为不同类型的无线设备服务定制的响应变得越来越重要。

假设您设计一个在线产品编目,而这个编目需要服务于各种功能不同的无线客户机。例如,一些客户机设备可能具有比较大的彩色显示器,而其他设备则可能具有比较小的黑白显示屏。一些客户机可能支持某种格式图像的显示,而其他客户机则可能支持另一种图像格式。

同样,有的客户机支持标记语言,而有的客户机则不支持标记语言。不同的无线设备和浏览器都支持以下三种流行的标记语言:

  1. WML
  2. XHTML
  3. Compact HTMLCHTML

这些标记语言还具有各种版本并且有很多差别,这意味着您需要应对无数种可能,以提供一种最适合特定的请求客户机的标记语言。

出于演示的目的,本教程在 多服务的餐馆菜单 一节中讨论四种 WML XHTML 标记。目前,您必须认识到:识别出试图访问编目应用程序的客户机是非常重要的。您的多服务编目应用程序识别客户机(换句话说,也就是它的制造商和型号),然后检查一些设备描述数据库以便了解请求客户机的功能。这使您的编目应用程序能够提供适合请求的客户机的响应。

万维网联盟(W 3C Mobile Web Initiative 理解了设备描述在普适应用程序中的重要性之后,发布了标题为 “Device Description Landscape” 的工作草案(请参阅 参考资料)。本草案讨论了描述设备功能以及相应地采用(或定制)响应的概念的几个方面。W 3C 的工作草案中所提到的设备描述计划之一就是名为 WURFL 的开放源码项目。本系列教程主要关注使用 WURFL 作为多服务应用程序中的设备描述格式。

 

WURFL 简介

WURFL SourceForge.net 上的一个开放源码项目。它定义设备描述的 XML 格式,并且包含有关名为 wurfl.xml XML 文件中无线设备的一些重要信息,这个文件可以从 WURFL 的官方网站下载(请参阅 参考资料)。

wurfl.xml 文件包含以下设备描述信息:

  • 无线设备的制造商和型号
  • 每种类型的无线设备的 user-agent 字符串:通常情况下,客户机(如无线设备)会将 user-agent 字符串随请求一起发送到 Web 服务器。特定制造商和型号的所有设备都使用相同的 user-agent 字符串。因此,可以使用这个 user-agent 字符串确定请求客户机。当 Web 服务器接收到请求(如 HTTP 请求)时,它从请求中提取 user-agent 字符串,并检查 wurfl.xml 文件以识别请求客户机并了解它的功能。探索和处理 WURFL 一节介绍识别设备并从 wurfl.xml 中读取其设备描述的整个过程。
  • 无线设备的某些功能:wurfl.xml 文件定义了这些功能对于不同设备的值。设备功能连同其相对于某种设备的值便形成了特定设备的实际设备描述。WURFL 定义的最重要的设备功能包括:显示字符、安全性相关特性以及无线设备支持的标记类型、样式、字体和图片格式。

下一节介绍如何在多服务应用程序中使用 WURFL

 

如何使用 WURFL

本教程介绍有关使用 WURFL 的三个方面:

  1. Java APIWURFL 包含综合的、功能全面的 Java API,可以使用它来处理 wurfl.xml。这个 API 只需知道来自请求客户机的 user-agent 字符串。它可以在内部解析 wurfl.xml,以了解发送 user-agent 字符串的设备的功能。本教程将介绍这个 API 的工作原理,并在 WURFL Java API 一节中构建一个示例应用程序以说明它的使用方法。
  2. WALLWURFL 随附的 JSP 标记库称为 WALL,用于构建多服务的 JSP 页面。WALL 标记内部使用 WURFL Java API 了解请求客户机设备的功能。探索 WALL 一节将详细介绍 WALL 以说明它如何使用 WURFL Java API 进行多服务,帮助您了解如何构建定制的多服务标记。构建定制的多服务标记 一节演示在 WURFL 的基础上构建您自己的多服务标记。
  3. 导航:服务器端 Web 应用程序几乎总是具有支持导航的多个页面。这意味着 Web 应用程序需要收集用户的数据,并维护服务器端对象,这些对象打包用户的数据并在客户机导航应用程序时将该数据从一个页面发送到下一个页面。JSF 是一个标准的 Java 框架,它定义了一种机制,能够在客户屏幕上呈现用户界面组件(如数据提交表单)的机制、收集用户数据并在服务器端管理这些数据。为了在实际 Java 应用程序中充分利用 WURFL 功能,需要将多服务支持整合到 JSF 中。这需要将 WURFL JSF 一起放到实际的、多页面以及多服务的 Web 应用程序中。本系列的第 2 部分介绍如何使用 WURFL 构建多服务 JSF 应用程序。

 

样本多服务应用程序简介

本教程通过构建一个餐馆的菜单应用程序来介绍多服务的概念,这个应用程序由两个 JSP 页面组成:multiServeMenuCategories.jsp multiServeDishes.jspmultiServeMenuCategories.jsp 页面提供特定于设备的标记,以显示餐馆菜单中包含的各种菜肴,如头盘或饭后甜点。multiServeDishes.jsp 页面提供特定于设备的标记,以显示特定菜单类别中包含的菜品。

WALL 已经提供了 multiServeMenuCategories.jsp 页面中所需的多服务标记,因此这个页面给您创造了详细研究 WALL 标记工作方式的机会。multiServeDishes.jsp 页面需要 WALL 中当前不可用的一些多服务支持,因此开发这个页面可以使您了解如何使用 WURFL Java API 构建您自己的定制多服务 JSP 标记。

注意,在构建餐馆菜单应用程序的导航支持之前,本教程第一部分(即本教程)中开发的两个 JSP 页面不会彼此链接,而这个导航支持需要将 JSF 整合到餐馆菜单应用程序中。这方面的内容将在第 2 部分进行介绍。

 

多服务的餐馆菜单

WURFL 定义重要设备的功能,名为 preferred_markup,它指定设备首选的标记语言。多服务应用程序检查 wurfl.xml 文件以便读取 preferred_markup 功能的值。应用程序知道了特定请求客户机的首选标记之后,将以此作为依据向该客户机提供服务。

探索 WALL 一节介绍了 preferred_markup 功能的使用。因此,首先让我们讨论一下可用于在各种无线设备的屏幕上显示菜单的不同标记。

下几节介绍可以用来在餐馆菜单上显示可供应的菜肴分类的各种标记。

 

使用 WML 呈现餐馆菜单

浏览一下 1,其中显示了 WinWAP Smartphone Browser Emulator 的屏幕上显示的菜单分类。


1. WinWAP Smartphone Browser Emulator 屏幕上显示的菜单分类

清单 1 显示了 WALL 所生成的 WML 标记,它显示 1 中所示的菜单分类。


清单 1. 用于在 WinWAP Smartphone Browser Emulator 上显示菜单分类的 WML 标记

                   

<?xml version="1.0"?>

<!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN"

"http://www.wapforum.org/DTD/wml_1.1.xml">

<wml>

    <head>

       <meta name="taglib" content="WALL" />

    </head>

    <card id="w" title="Restaurant Menu">

        <p>

           <a href="http://www.AFictitiousRestaurant.com/starter"

              title="Starters">

              Starters

           </a><br/>

           <a href="http://www.AFictitiousRestaurant.com/main"

              title="Main Course">

              Main Course

           </a><br/>

           <a href="http://www.AFictitiousRestaurant.com/desserts"

              title="Desserts">

              Desserts

           </a><br/>

        </p>

    </card>

</wml>

 

注意,清单 1 中的 WML 标记使用一个 <p> 标记包装了大量 <a> 标记,其中每个 <a> 标记代表一个菜单分类。<a> 标记的作用是使每个分类成为一个超链接,以使 WinWAP Smartphone Browser Emulator 的用户可以激活这个链接,查看菜单分类中所包含的菜肴。

2 显示了 Openwave Phone Simulator Version 5.1,其中显示了 1 所示的相同菜单分类。


2. Openwave Phone Simulator 5.1 上显示的菜单分类

清单 2 显示了在 Phone Simulator 5.1 上显示菜单分类所需的 WML 代码。


清单 2. Phone Simulator 5.1 屏幕上显示菜单分类的 WML 标记

                   

<?xml version="1.0"?>

<!DOCTYPE wml PUBLIC "-//PHONE.COM//DTD WML 1.1//EN"

"http://www.phone.com/dtd/wml11.dtd" >

 

<wml>

    <head>

       <meta name="taglib" content="WALL" />

    </head>

 

    <card id="w" title="Restaurant Menu">

       <p align="left" mode="nowrap">

          <select>

              <option onpick="http://www.AFictitiousRestaurant.com/starter"

                  title="Starters">

                  Starters

              </option>

              <option onpick="http://www.AFictitiousRestaurant.com/main"

                  title="Main Course">

                  Main Course

              </option>

              <option onpick="http://www.AFictitiousRestaurant.com/desserts"

                  title="Desserts">

                  Desserts

              </option>

          </select>

       </p>

    </card>

</wml>

 

将清单 1 2 对比之后,将会发现 清单 2 中的 WML 代码使用 <select> 标记来包含大量的 <option> 标记。这意味着 2 实际上是在使用 <select> 以及 <option> 子标记的基础之上显示了 WML 菜单。建议使用 <select> 标记在 Phone Simulator 5.1 屏幕上显示菜单项。这也就是 WALL 为使用 Phone Simulator 5.1 的客户机生成 <select> 标记的原因。

 

使用 XHTML

现在,让我们观察一下 Openwave Phone Simulator Version 7.0,它是设备模拟器的一个示例,其首选标记是 XHTML。假设您想在 Phone Simulator 7.0 模拟器屏幕上提供餐馆菜单,如 3 所示。


3. Phone Simulator 7.0 屏幕上显示的餐馆菜单

清单 3 显示了在 Phone Simulator 7.0 屏幕上显示餐馆菜单所使用的 XHTML 标记。


清单 3. Phone Simulator 7.0 屏幕上显示菜单分类的 XHTML 标记

                   

<?xml version="1.0"?>

<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.0//EN"

"http://www.wapforum.org/DTD/xhtml-mobile10.dtd">

 

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">

    <head>

       <title>Restaurant Menu</title>

    </head>

 

    <body>

       <ol>

           <li>

               <a accesskey="1"

                  href="http://www.AFictitiousRestaurant.com/starter"

                  title="Starters">

                  Starters

               </a>

           </li>

           <li>

               <a accesskey="2"

                  href="http://www.AFictitiousRestaurant.com/main"

                  title="Main Course">

                  Main Course

               </a>

           </li>

           <li>

               <a accesskey="3"

                  href="http://www.AFictitiousRestaurant.com/desserts"

                  title="Desserts">

                  Desserts

               </a>

           </li>

       </ol>

    </body>

</html>

 

可以看到 清单 3 中的 XHTML 标记使用 <ol> 标记,以有序列表的形式包含餐馆菜单中的各个分类。 4 显示以一种更让人着迷的方式来提供菜单分类,使用两种颜色区分不同的菜单分类视图。


4. 以一种更让人感兴趣的方式提供菜单种类

清单 4 显示生成 4 中屏幕截图的 XHTML 标记。注意 清单 4 使用 <table> XHTML 标记包含不同的分类。每个分类都成为一个表的单元格。


清单 4. 使用颜色和表支持显示菜单分类的 XHTML 标记

                   

<?xml version="1.0"?>

<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.0//EN"

"http://www.wapforum.org/DTD/xhtml-mobile10.dtd">

 

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">

    <head>

      <title>Restaurant Menu</title>

      <style>

          .bgcolor1 { background-color:#99CCFF;}

          .bgcolor2 { background-color:#FFFFFF;}

      </style>

    </head>

 

    <body>

        <table>

           <tr>

              <td class="bgcolor1"> 1

                 <a accesskey="1"

                    href="http://www.AFictitiousRestaurant.com/starter"

                    title="Starters">

                    Starters

                 </a>

              </td>

           </tr>

           <tr>

              <td class="bgcolor2"> 2

                 <a accesskey="2"

                    href="http://www.AFictitiousRestaurant.com/main"

                    title="Main Course">

                    Main Course

                 </a>

              </td>

           </tr>

           <tr>

              <td class="bgcolor1"> 3

                 <a accesskey="3"

                    href="http://www.AFictitiousRestaurant.com/desserts"

                    title="Desserts">

                    Desserts

                 </a>

              </td>

           </tr>

        </table>

    </body>

</html>

 

另外请注意,清单 4 包括 <style> 标记,该标记定义两种背景颜色。每个 <td> 标记(位于 清单 4 中)都具有一个引用背景颜色的类属性。该类属性的值在相邻的 <td> 标记中是不同的,而在 <td> 标记中是相同的。以这种方法使用类属性,就为每个分类呈现出了不同的背景颜色。

并不是所有的 XHTML 设备都支持在表中显示时使用颜色。因此,WALL 需要检查请求客户机支持哪些 XHTML 功能。如果它支持使用具有彩色单元的表格,则提供 清单 4 中的 XHTML 标记;否则提供 清单 3

 

借助 WALL 生成菜单种类

上面您已经看到了为显示菜单分类,不同的设备需要生成的四种不同的标记示例。WALL 提供多服务标记,可以用于:

  • 自动检测请求客户机设备的功能
  • 提供最适合请求客户机的标记

清单 5 显示一个名为 multiServeMenuCategories.jsp JSP 页面,该页面使用 WALL 的多服务标记生成各种特定于设备的标记(前面在清单 123 4 中给出的标记)。


清单 5. multiServeMenuCategories.jsp

                   

<%@ taglib uri="/WEB-INF/tld/wall.tld" prefix="wall" %>

<wall:document>

   <wall:xmlpidtd />

 

   <wall:head>

       <wall:title>Restaurant Menu</wall:title>

       <wall:menu_css />

   </wall:head>

 

   <wall:body>

       <wall:menu colorize="true" autonumber="true">

           <wall:a href="http://www.AFictitiousRestaurant.com/starter"

              title="Starters">

              Starters

           </wall:a>

           <wall:a href="http://www.AFictitiousRestaurant.com/main"

              title="Main Course">

              Main Course

           </wall:a>

           <wall:a href="http://www.AFictitiousRestaurant.com/desserts"

              title="Desserts">

              Desserts

           </wall:a>

       </wall:menu>

   </wall:body>

</wall:document>

 

清单 5 中可以看到,需要使用各种 WALL 标记以便多服务于菜单分类:

  • <wall:document>:该标记只包含一个 WALL 文档。<wall:xmlpidtd> 标记(位于 <wall:document> 标记中)根据请求客户机的首选标记创建 XML 头部(header)。
  • <wall:menu_css>:该标记位于 <wall:head> 标记内,提供相关样式的标记,如 清单 4 中所示。如果设备不支持使用这些样式,则 <wall:menu_css> 不会生成这种样式的标记。
  • <wall:menu>:该标记位于 <wall:body> 标记内,包含大量 <wall:a> 标记。每个 <wall:a> 标记都多服务于一个菜单分类(例如,头盘或饭后甜点)。

下几节介绍这些 WALL 标记如何在内部使用 WURFL 功能多服务于不同类型的标记。最后一节介绍如何构建您自己的多服务标记以及如何在 multiServeDishes.jsp 页面中使用这些标记以呈现特定于设备的标记。

 

探索和处理 WURFL

WURFL 标记

wurfl.xml 文件只包含一个 <devices> 标记,该标记中包含大量的 <device> 标记。每个 <device> 标记都代表一个无线设备。清单 6 显示了 <devices> 标记的一种简化形式。


清单 6. 包含大量 <device> 标记的 <devices> 标记

                   

<devices>

 

    <device id="generic" user_agent="" fall_back="root">

        <group id="markup">

            <capability name="preferred_markup

" value="

wml_1_1"/>

            <capability name="wml_1_1" value="true"/>

        </group>

        <group id="wml_ui">

            <!--Device capabilities-->

        </group>

        <group id="xhtml_ui">

            <!--Device capabilities-->

        </group>

        <group id="chtml_ui">

            <!--Device capabilities-->

        </group>

        <!--Other groups of capabilities-->

    </device>

 

    <device id="nokia_generic

" user_agent="

Nokia

" fall_back="

generic">

       <!--Groups of device capabilities-->

    </device>

 

    <device id="nokia_3300_ver1_sub425"

        user_agent="Nokia3300/1.0 (4.25)

        Profile/MIDP-1.0 Configuration/CLDC-1.0"

        fall_back="nokia_3300_ver1"/>

       <!--Groups of device capabilities-->

    </device>

 

    <!--Other devices-->

 

</devices>

 

注意,每个 <device> 标记都有三个属性 —— iduser_agent fall_back —— 以及很多 <group> 标记。id 属性只定义设备的标识符(或 ID)。user_agent 属性包含一个 user-agent 字符串,该字符串将包含在设备向 Web 应用程序发送的 HTTP 请求中。WALL 使用该字符串标识请求客户机。例如,清单 7 显示 Nokia 3300 设备的 HTTP 请求。


清单 7. Nokia 3300 设备的 HTTP 请求

                   

GET /MultiServeRestaurantMenu/multiServeMenuCategories.jsp HTTP/1.0

User-Agent: Nokia3300/1.0 (4.25) Profile/MIDP-1.0 Configuration/CLDC-1.0

Host: localhost:8080

Accept: text/vnd.wap.wml, image/vnd.wap.wbmp,

        application/vnd.wap.wmlc, application/vnd.wap.wmlscriptc

 

可以看到,Nokia3300/1.0 (4.25) Profile/MIDP-1.0 Configuration/CLDC-1.0 字符串包含在 HTTP 请求的 User-Agent 参数中。这个字符串构成了 清单 6 中第三个 <device> 标记的 user_agent 属性的值。

WURFL 处理应用程序(例如之前看到的 multiServeMenuCategories.jsp 页面)将通过以下步骤确定哪个无线设备正在发送 HTTP 请求:

  1. 查看 User-Agent 参数。
  2. wurfl.xml 文件中查找 user_agent 属性值相匹配的 <device> 标记。

WURFL 随附的 Java API 提供了这种处理 HTTP 请求、检查 wurfl.xml 文件了解设备功能的能力。WURFL Java API 一节介绍 Java API 的工作方式以及如何在多服务应用程序中使用它,在此我们首先讨论 清单 6 <device> 标记的 第三个属性(fall_back)的作用和用法。

 

WURFL 中的一般设备和专门设备

WURFL 设计了一种机制,可以定义一般设备,再使用一般设备描述来指定实际设备功能。这种机制使用 fall_back 属性工作。

清单 6 中的第一个设备标记(它的 id 属性值为 generic)表示最一般的设备。 清单 6 中第二个 <device> 标记的 fall_back 属性值为 generic。这意味着第二个 <device> 标记使用(或专门处理)一般设备的设备描述。

还要注意,清单 6 中第二个 <device> 标记的 id 属性值为 nokia_generic。这意味着第二个 <device> 标记是所有 Nokia 设备的最一般形式。实际的 Nokia 设备使用通用 Nokia 设备的设备描述。当设备在 wurfl.xml 文件中指定一般设备时,只需要描述它自己具备但在一般设备中缺少或与采用一般形式定义的功能不同的功能即可。

因此,需要跟随 wurfl.xml 文件中的返回路径以了解设备的功能。为了跟随返回路径,可以读取 fall_back 属性值,然后查找具有匹配 ID <device> 标记,以便找到更为一般的设备描述。处理 wurfl.xml 文件 一节将详细介绍返回路径。

现在,让我们看看 WURFL 如何使用 <device> 标记描述无线设备的功能。

 

定义设备功能

WURFL 使用 <group> 标记将设备的相关功能组合到一起。例如,可以在 清单 6 中找到 <group> 标记,它的 id 属性值为 markup

<group> 标记中的每个功能都由 <capability> 标记表示。<group> 标记可以包含很多 <capability> 标记。

WURFL 将设备功能定义为名称/值对。每个 <capability> 标记都具有 name 属性,它的值定义设备的功能。例如,清单 6 包含一个 <capability> 标记,它的 name 属性为 preferred_markup。这个 <capability> 标记指定无线设备的首选标记。

<capability> 标记还具有 value 属性,它指定功能的值。例如,清单 6 中一般设备的 preferred_markup 功能的值为 wml_1_1,这意味着通用设备使用 WML 1.1 作为它的首选标记。

 

一些重要的功能组

WURFL 定义了几组设备功能,其中最重要的功能组与标记和用户界面有关。清单 8 包含三个这样的 <group> 标记。


清单 8. 一些重要的设备功能组

                   

<group id="markup">

    <capability name="preferred_markup" value="wml_1_1"/>

    <capability name="wml_1_1" value="true"/>

    <capability name="wml_1_2" value="false"/>

    <capability name="wml_1_3" value="false"/>

    <capability name="html_wi_w3_xhtmlbasic" value="false"/>

    <capability name="html_wi_oma_xhtmlmp_1_0" value="false"/>   

    <!--Other markup capabilities-->

</group>

 

<group id="wml_ui">

    <capability name="menu_with_select_element_recommended" value="false"/>

    <capability name="card_title_support" value="true"/>

    <capability name="access_key_support" value="false"/>

    <capability name="table_support" value="true"/>

    <capability name="numbered_menus" value="false"/>

    <capability name="menu_with_list_of_links_recommended" value="true"/>

    <!--Other wml_ui capabilities-->

</group>

 

<group id="xhtml_ui">

    <capability name="xhtml_supports_css_cell_table_coloring" value="false"/>

    <capability name="xhtml_supports_forms_in_table" value="false"/>

    <capability name="xhtml_honors_bgcolor" value="false"/>

    <capability name="xhtml_select_as_dropdown" value="false"/>

    <capability name="xhtml_table_support" value="false"/>

    <!--Other xhtml_ui capabilities-->

</group>

 

注意,每个 <group> 标记都具有 id 属性,它标识组。例如,清单 8 id markup 的组包含所有与标记相关的功能。同样,id wml_ui 的组包含设备的所有功能,这些功能与 WML 中表示用户界面的组件相关。例如,名为 menu_with_select_element_recommended 的功能指定是否建议使用 <select> 标记在 WML 无线设备的屏幕上显示菜单项。

id xhtml_ui 的组包含 XHTML 用户界面功能。例如,名为 xhtml_supports_css_cell_table_coloring 的功能指定是否可以使用颜色在 XHTML 无线设备的屏幕上显示表单元格。

上面已经介绍了 WURFL 如何使用 <devices><device><group> <capability> 标记以及它们的属性指定无线设备的功能。下一节介绍如何跟随返回路径并且处理 wurfl.xml 文件,以了解有关设备功能的信息。

 

处理 wurfl.xml 文件

假设您想使用 wurfl.xml 文件来了解是否建议为特定请求客户机设备使用 WML <select> 标记。为此,您需要的一切就是客户机设备的 HTTP 请求和 wurfl.xml 文件。如果有了这些内容,就可以执行以下步骤来了解请求客户机的功能:

  1. HTTP 请求中读取 user-agent 字符串。
  2. user_agent 属性与 user-agent 字符串匹配的 wurfl.xml 文件中查找 <device> 标记。
  3. 在第 2 步的 <device> 标记中查找 wml_ui 功能组。
  4. 检查设备的功能组中是否存在一个名为 menu_with_select_element_recommended 的功能。如果存在,则读取这个功能的值。已经发现了所要查找的内容,则不再需要进一步处理。
  5. 如果这个功能不存在,则跟随返回路径。查找 id 属性与第 2 步的 <device> 标记的 fall_back 属性相匹配的 <device> 标记。
  6. 使用第 5 步的 <device> 标记重复第 3 步到第 5 步。继续重复,直到找到 menu_with_select_element_recommended 功能或者达到最一般的 <device> 标记为止。

这里的工作量非常大,上面列出的 6 个步骤只是为您展示了 WURFL 的工作方式。但 WURFL Java API 会为您处理所有这些难题,因此您将不再需要亲自处理 wurfl.xml 文件。

下一节介绍 WURFL Java API 的工作方式。

 

WURFL Java API

Java API 中的类

WURFL Java API 包含几个类,这些类可以帮助您轻松使用 WURFL 功能。 5 显示了 WURFL 应用程序中的 WURFL 类。


5. WURFL Java API 中的类与一个 WURFL 应用程序

注意,WURFL Java API 由以下四个类组成:WurflObjectsManagerUAManager CapabilityMatrixWURFL 应用程序使用 ObjectsManagerUAManager CapabilityMatrix 了解设备的功能。这三个 WURFL 类反过来使用 Wurfl 类的功能,它包含 wurfl.xml 文件,并公开其他三个 WURFL 类将使用的方法。

本节首先介绍 Wurfl 类的功能,然后介绍其他 WURFL 类(也就是 ObjectsManagerUAManager CapabilityMatrix)如何使用 Wurfl 类进行多服务。最后介绍 WURFL 应用程序如何使用 WURFL 类。

 

Wurfl

Wurfl 类具有几个构造函数,所有构造函数都是受保护的,因此不能直接实例化 Wurfl 类。但可以使用 ObjectsManager 类实例化 Wurfl 对象。 ObjectsManager 一节介绍 Wurfl 对象的实例化。

具有一个参数的 Wurfl 构造函数将 wurfl.xml 文件的路径作为参数。它解析 wurfl.xml 文件的内容,并且准备好服务于特定设备功能的请求。

另一个 Wurfl 构造函数采用两个字符串类型的参数。第一个参数指定 wurfl.xml 文件的路径,第二个参数指定另一个称为补丁文件的文件的路径。WURFL 利用这个补丁文件允许应用程序更新设备描述,由于具有改良功能的新无线设备不断涌现,因此这种更新非常重要。可以使用 WURFL 补丁文件使设备描述保持最新。

另一个 Wurfl 构造函数将两个 File 对象作为参数。第一个 File 对象保存 wurfl.xml file 文件,第二个 File 对象保存补丁文件。

所有这些构造函数都提供相同的服务,即它们打开 wurfl.xml 文件(以及补丁文件,如果可用的话)、解析它的内容(设备、返回路径、组以及功能)、将内容加载到内部变量中以及准备 Wurfl 对象以提供有关特定设备功能的信息。

现在,让我们讨论一下 Wurfl 类中的重要方法。

 

Wurfl 类的方法

WURFL Java API 中的其他类使用这些 Wurfl 类方法来了解设备功能:

  • isCapabilityDefinedInDevice()
  • getCapabilityValueForDeviceAndCapability()
  • getDeviceIDFromUA()
  • getDeviceIDFromUALoose()

isCapabilityDefinedInDevice() 方法检查设备的功能并且接受两个字符串类型的参数。第一个参数是要检查其功能的设备的标识符(或者简写为 ID)。第二个参数指定要检查的功能的名称。isCapabilityDefinedInDevice() 方法只有在这个功能是为特定设备定义的情况下才返回 true;否则返回 false

getCapabilityValueForDeviceAndCapability() 方法同样接受两个参数,并且返回设备功能的值。该方法搜索整个返回路径以查找设备功能的值。

getDeviceIDFromUA() 方法采用 user-agent 字符串(如 清单 7 中的 Nokia3300/1.0 (4.25) Profile/MIDP-1.0 Configuration/CLDC-1.0), 并且返回使用这个 user-agent 字符串的设备的标识符。如果想使用 getDeviceIDFromUA() 方法,则必须首先从 HTTP 请求(如在 清单 7 中看到的 HTTP 请求)中提取 user-agent 字符串。

getDeviceIDFromUALoose() 方法会自行完成此类额外的处理。您可以将完整的 HTTP 请求传递给 getDeviceIDFromUALoose() 方法,该方法将在内部处理 HTTP 请求、从 HTTP 请求中提取 user-agent 字符串并且告诉您发送这个 HTTP 请求的设备的 ID

 

ObjectsManager

ObjectsManager 类包含 Wurfl 类的功能并且提供几个有用的静态方法。例如,ObjectsManager 具有一个名为 getWurflInstance() 的方法,该方法不接受任何参数,并返回 Wurfl 类的一个实例。为了实例化 Wurfl 对象,getWurflInstance() 方法需要知道查找 wurfl.xml 文件的位置。getWurflInstance() 方法首先在当前目录中打开 wurfl.properties 文件。该文件在名为 wurflpath 的属性中包含 wurfl.xml 文件的路径。然后 getWurflInstance() 方法使用 wurfl.xml 文件的路径实例化 Wurfl 对象。

使用 WURFL Java API 了解设备的功能 一节中使用 wurfl.properties 文件。

ObjectsManager 类还提供名为 getUAManagerInstance() 的静态方法,该方法实例化并返回 UAManager 对象。在实例化 UAManager 对象之前,getUAManagerInstance() 方法首先检查是否存在 Wurfl 类的实例。如果不存在,则实例化 Wurfl 对象(如讨论 getWurflInstance() 方法时的说明),并将 Wurfl 对象放置到 UAManager 对象中。UAManager 对象充当 Wurfl 对象的一个非常有用的包装器。

同样,ObjectsManager 类具有另一个名为 getCapabilityMatrixInstance() 的方法,该方法返回 CapabilityMatrix 对象。CapabilityMatrix 还包含 Wurfl 对象。UAManager CapabilityMatrix 类都提供易于使用的 Wurfl 功能,将在下一节中介绍这些功能。

 

UAManager CapabilityMatrix

UAManager 类具有两个方法:getDeviceIDFromUA() getDeviceIDFromUALoose()。在前面的 Wurfl 类的方法 一节的 Wurfl 类中也具有同样两个方法。UAManager 类的方法内部使用 Wurfl 类的各个方法,因此提供相同的功能。

在服务器端应用程序中使用 UAManager 类的主要优势在于:它会记住应用程序之前搜索的 user-agent 和设备 ID 字符串。当 UAManager 收到设备 ID 请求时,它首先检查它自己的缓存,查明缓存中是否已经存在所需的 ID。如果找到了这个 ID,则直接返回 ID,而不检查 Wurfl 类。UAManager 只有在未在其缓存中找到所需设备 ID 时才调用 Wurfl 方法。因此,UAManager 类的方法比 Wurfl 类的方法运行效率更高。这在服务器端应用程序中是非常有用的,在服务器端应用程序中您可以对相同设备标识符进行重复请求。

CapabilityMatrix 类提供一些方法,这些方法可以在内部使用 Wurfl 类的方法来提供与检查设备功能有关的能力。CapabilityMatrix 类的重要方法包括 isCapabilityDefinedInDevice() getCapabilityForDevice()。这两个方法类似于在前面的 Wurfl 类的方法 一节中看到的 isCapabilityDefinedInDevice() getCapabilityValueForDeviceAndCapability() 方法。 CapabilityMatrix Wurfl 类的方法之前的惟一差别是 CapabilityMatrix 类缓存以前搜索的功能数据。因此,CapabilityMatrix 类的方法比 Wurfl 方法更高效。

下一节介绍如何使用 ObjectsManagerUAManager CapabilityMatrix 了解设备的功能。

 

使用 WURFL Java API 了解设备的功能

清单 9 显示名为 CapabilityTester Java 应用程序,该程序使用 WURFL Java API 的类查找和打印无线设备的几个重要功能的值。


清单 9. CapabilityTester

                   

public class CapabilityTester

{

    public static void main (String[] args)

    {

        /*** Step 1 ***/

        String httpRequestString = readWirelessDeviceHTTRequest(args[0]);

   

        /*** Step 2 ***/

        CapabilityTester capabilityTester =

            new CapabilityTester(httpRequestString);

    }//main

 

    public CapabilityTester (String httpRequest)

    {

        /*** Step 3 ***/

        UAManager uaManager = ObjectsManager.getUAManagerInstance();

 

        /*** Step 4 ***/

        String device_id =

            uaManager.getDeviceIDFromUALoose(httpRequest);

   

        /*** Step 5 ***/

        CapabilityMatrix capabilityMatrix =

            ObjectsManager.getCapabilityMatrixInstance();

 

        /*** Step 6 ***/

        String markup = capabilityMatrix.getCapabilityForDevice(

            device_id,

            "preferred_markup");

 

        String access_key_support = capabilityMatrix.getCapabilityForDevice(

            device_id,                                

            "access_key_support");

 

        String table_support = capabilityMatrix.getCapabilityForDevice(

            device_id,

            "xhtml_supports_table_for_layout");

 

        String css_cell_support = capabilityMatrix.getCapabilityForDevice(

            device_id,

            "xhtml_supports_css_cell_table_coloring");

 

        String menu_with_select_element_support =

            capabilityMatrix.getCapabilityForDevice(

                device_id,

                "menu_with_select_element_recommended");

 

        System.out.println ("/nDevice ID                         : "+device_id);

        System.out.println ("preferred_markup                    : "+markup);

        System.out.println ("access_key_support                  : "+access_key_support);

        System.out.println ("menu_with_select_element_recommended: "+

                             menu_with_select_element_support);

        System.out.println ("xhtml_supports_table_for_layout     : "+table_support);

        System.out.println ("xhtml_supports_css_cell_table_coloring: "+css_cell_support);

 

    }//CapabilityTester

   

    private static String readWirelessDeviceHTTRequest (String requestFilePath)

    {

        try {

            FileInputStream fileStream = new FileInputStream (requestFilePath);

 

            if (fileStream != null)

            {

                int length = 0;

                byte data[] = new byte [fileStream.available()];

                fileStream.read(data);

                return new String (data);

            }

        } catch (java.io.IOException ie){

            ie.printStackTrace();

        }

        return null;    

    }//readWirelessDeviceHTTRequest

   

}//CapabilityTester

 

注意,CapabilityTester 类执行以下步骤使用 ObjectsManagerUAManager CapablityMatrix 类:

  1. 首先,CapabilityTester main() 方法从名为 HTTPRequest.txt 的文件中读取 HTTP 请求。 可以对 CapabilityTester 使用任何请求。
  2. 然后,main() 方法实例化 CapabilityTester 类,将 HTTP 请求传递给 CapabilityTester 构造函数。
  3. CapabilityTester 构造函数调用 ObjectsManager.getUAManager() 方法,该方法返回 UAManager 实例。
  4. 为了实例化 Wurfl 对象,getUAManager() 方法在内部查找 wurfl.properties 文件并且从属性文件中读取 wurfl.xml 文件的路径。然后将这个路径传递给 Wurfl 构造函数。
  5. 最后,getUAManager() 方法实例化 UAManager 对象,将 Wurfl 对象传递给 UAManager 构造函数。
  6. 现在,CapabilityTester 构造函数调用 UAManager 类的 getDeviceIDFromUALoose() 方法。将第一步中的 HTTP 请求传递给 getDeviceIDFromUALoose() 方法,该方法内部使用 Wurfl 对象执行以下任务:
    1. 解析 HTTP 请求以从 HTTP 请求中提取 user-agent 字符串。
    2. 使用 Wurfl 对象获取发送 HTTP 请求的设备的 ID
  7. 现在,CapabilityTester 构造函数调用 ObjectsManager 类的 getCapabilityMatrixInstance() 方法,以获取 CapabilityMatrix 类的实例。
  8. CapabilityTester 构造函数调用 CapabilityMatrix 对象的 getCapabilityForDevice() 方法以了解在第 4 步中获得其 ID 的设备的重要功能。在此步中探讨的设备功能包括在前面的 定义设备功能 一节中的 preferred_markup 功能。清单 9 还包括一些其他功能:
    1. xhtml_supports_table_for_layout:指定设备是否能够使用 XHTML 表来控制用户界面组件的布局。
    2. xhtml_supports_css_cell_table_coloring:确保设备可以使用颜色来显示表。
    3. access_key_support:告诉设备是否允许网页中的用户界面组件(如链接、选择列表等)访问无线设备的数字小键盘。

最后讨论使用 WURFL 类查找设备功能。您已经具备了所有基础知识,接下来将了解 WALL 标记库(已在前面的 如何使用 WURFL 一节中介绍)如何使用这些类进行多服务。

 

探索 WALL

WALL 体系结构

前文已经向您介绍了在 WURFL Java API 中使用类的方法。可以在服务器端 Java 应用程序中使用这些类,以了解请求客户机的功能并且相应地多服务于响应。

JSP 技术允许您以可重用的定制 JSP 标记的形式开发其扩展。因此,在服务器端 Java 应用程序中使用 WURFL 功能的最简单方法是开发和使用定制的多服务 JSP 标记。

WALL 包括很多有用的多服务 JSP 标记,如 <body><menu> <a>。每个标记都附带一个标记句柄类,该类按照 JSP 中的定制标记处理机制工作。标记句柄类为它们各自的标记创建多服务标记。

本节介绍 WALL 的标记句柄类如何使用 WURFL ObjectsManagerUAManager CapabilityMatrix 类进行多服务。

先让我们浏览一下 6 中的 WALL 体系结构。


6. WALL 体系结构

正如您所看到的,WALL 标记库中的类需要来自三类组件的较低级别的支持:

  1. WURFL 处理类:在前面的 WURFL Java API 一节中介绍的类。
  2. JSP 的定制标记支持机制:本节将介绍这种机制。
  3. 标记实用工具类 TagUtil该类提供一些实用功能,并包含有助于 WALL 的标记句柄类处理 HTTP 请求以及使用 wurfl.xml 文件的一些有用的方法。 3 步:使用 WURFL 处理类 一节中讨论了几种 TagUtil 方法。

本节的其余部分介绍 WALL 实现如何使用三种类型的组件。

 

定制 JSP 标记的工作原理

JSP 中的定制标记以标记库的形式分发。每个标记库包含一个标记库描述符(TLD)文件,该文件是一个指定标记库中所有标记的详细信息(如定制标记的名称、它的属性以及标记句柄类的完全限定的名称)的 XML 文件。清单 10 WALL TLD 文件的摘录,该文件定义 WALL <menu> 标记。


清单 10. WALL 的菜单标记的 TLD 条目

                   

<tag>

    <name>menu</name>

    <tag-class>net.sourceforge.wurfl.wall.WallMenu</tag-class>

    <body-content>JSP</body-content> 

    <description>Multiserve Menu</description>

    <attribute>

        <name>colorize</name>

        <required>false</required>

    </attribute>

    <attribute>

        <name>autonumber</name>

        <required>false</required>

    </attribute>

 </tag>

 

可以看到 <menu> 标记的 TLD 条目由一个名为 tag 的元素组成,该元素包含名为 nametag-classbody-content description 的几个子标记以及几个 attribute 标记。<name> <tag-class> 标记分别包含标记(也就是 menu)的名称以及菜单的标记句柄类(例如 net.sourceforge.wurfl.wall.WallMenu)的完全限定名。<body-content> 标记指定位于 WALL <menu> 标记中的内容类型。如果回过头去看一下 清单 5 multiServeMenuCategories.jsp 页面,您将会发现 WALL <menu> 标记包含 JSP 代码。这意味着 <menu> 标记的内容就是 JSP<description> 标记提供人类可读的简短标记描述。

清单 10 中的每个 <attribute> 标记都包含 WALL <menu> 标记的属性的详细信息。清单 10 显示两个 <attribute> 标记,每个标记都有两个子标记(<name> <required>)。<name> 子标记指定属性的名称,<required> 标记指定属性是必需的还是可选的。

定制的标记处理机制自动实例化与 JSP 页面中定制标记相应的标记句柄对象。它还为标记句柄对象中定制标记的每个属性设置属性值。例如,考虑 清单 5 multiServeMenuCategories.jsp 页面的 <wall:menu> 标记。它包含两个名为 colorize autonumber 的属性。因此,执行 multiServeMenuCategories.jsp 页面时,定制标记处理机制将实例化 WallMenu 对象(WALL <menu> 标记的标记句柄),从 JSP 页面中读取 colorize autonumber 属性的属性值,并且在 WallMenu 对象中设置属性值。

现在,让我们看一看 WallMenu 标记句柄类的工作方式。

 

Wall <menu> 标记的句柄类

所有标记句柄类(如 WallMenu 类)都都需要实现一个名为 javax.servlet.jsp.tagext.Tag 的接口,允许标记句柄创建需要创建的任何标记。例如,WallMenu 类需要创建包含菜单项的多服务标记,如菜单分类。

只要在 JSP 文件中遇到一个开始的 <menu> 标记(例如,清单 5 multiServeMenuCategories.jsp 页面的 <wall:menu> 标记)时,定制标记处理机制都会调用 WallMenu 类中名为 doStartTag() 的方法。同样,只要遇到一个结束的 <menu> 标记(例如,清单 5 中的 </wall:menu> 标记),都会调用 WallMenu 类的 doEndTag() 方法。

如您所料,doStartTag() doEndTag() 都是在 Tag 接口中进行定义的,并且 WallMenu 类实现这些方法中的所有多服务逻辑。下几节将详细介绍 WallMenu 类如何实现这些方法。您将掌握关于 WALL 的足够知识,以便在下一节中构建自己的多服务标记。

 

WallMenu.doStartTag() 方法

WallMenu 类扩展一个名为 javax.servlet.jsp.tagext.TagSupport 的类,这是 JSP 中定制标记支持机制的一部分。TagSupport 类实现 Tag 接口。不要直接实现 Tag 接口,最好是扩展 TagSupport,因为它提供了一些非常有帮助的实用方法。 2 步:使用 TagSupport 部分中介绍了一些 TagSupport 方法的使用。

在创建多服务标记时,WallMenu.doStartTag() 方法执行几个任务,我们将在以下四个步骤中对这些任务进行介绍:

  1. 初始化一些变量以控制多服务行为。
  2. 使用 TagSupport 类中的方法获取客户机的 HTTP 请求。
  3. 使用 WURFL 处理类了解请求客户机设备的首选标记。
  4. 根据首选标记显示 XHTMLWML CHTML 标记。

接下来的四部分将介绍这四个步骤。

 

1 步:初始化变量

清单 11 展示了构成 doStartTag() 方法的第 1 步的代码。


清单 11. 初始化变量以控制多服务行为

                   

public int doStartTag() throws JspException {

    /*** Step1 ***/ 

    current_bgcolor = "bgcolor2";

    autonumber_index = 0;

    mark_up = "";

    table_ok = "";

    css_ok = "";

    table_and_css_background = false;

    menu_css_tag = false;

   

     <!--Other steps in the doStartTag() method-->

}//doStartTag()

 

可以看到,第一步是初始化一些根据设备功能控制标记呈现情况的变量。例如,名为 markup 的变量包含一个确定请求客户机首选标记的字符串。另一个名为 table_ok 的变量包含一个字符串标识符,它可判断请求客户机能否显示 XHTML 表。css_ok 变量包含一个判断请求客户机是否接受样式表的字符串。

doStartTag() 方法在使用 WURFL Java API 检查 wurfl.xml 文件之后,在 3 中设置这些变量的值。

 

2 步:使用 TagSupport

初始化变量之后,doStartTag() 方法调用 TagSupport.findAncestorWithClass() 方法,如 清单 12 所示。


清单 12. 获取客户机的 HTTP 请求

                   

public int doStartTag() throws JspException {

 

    /***Lines of code for Step 1***/

 

    /*** Step2 ***/ 

    WallBlock wblock =

    (WallBlock) findAncestorWithClass(

        this,

        WallBlock.class);

 

    if (wblock != null) {

    throw new JspException(

         "'menu' tag cannot be nested inside a 'block'"+

         "tag (breaks XHTML validity and will produce "+

         "an error on some browsers)./n Close or remove the containing 'block' tag.");

    }

 

    request = (HttpServletRequest) pageContext.getRequest();

 

    <!--Other steps in the doStartTag() method-->

}

 

findAncestorWithClass() 方法在 JSP 页面中搜索某个标记的特定祖先,如 清单 5 中的 <wall:document> 标记,它是 <wall:menu> 标记的祖先。findAncestorWithClass() 方法接受两个参数。第一个参数指定希望查找其祖先的标记的句柄类。第二个参数指定所查找的祖先标记的句柄类。

清单 12 中的 findAncestorWithClass() 调用 this 作为第一个参数传递,net.sourceforge.wurfl.wall.WallBlock.class 作为第二个参数传递。使用这两个参数值的原因是 doStartTag() 方法正在查找其标记句柄类名为 WallBlock 的祖先。

WallBlock 标记句柄类表示名为 block WALL 标记。<block> 标记包含 JSP 页面中的其他几个 WALL 标记。<block> 标记的作用是正确包含这些标记的多服务标记,以便得到的标记将对所有目标设备起作用。但是,<block> 标记不包含 <menu> 标记。这意味着 <menu> 标记设计为在没有 <block> 包装器的情况下工作。

如果 JSP 创作者错误地在 JSP 页面的 <block> 标记中包含了一个 <menu> 标记,则所得到的标记将无法在所有目标设备上正常工作。因此,如果 清单 12 中的 findAncestorWithClass() 方法调用能够找到 <block> 祖先,则 doStartTag() 方法抛出一个异常,内容为<menu> 标记不能嵌入在 <block> 标记内。

确保 <menu> 标记未包含在 <block> 包装器之内后,doStartTag() 方法获取一个 HttpServletRequest 对象。HttpServletRequest 对象包含来自客户机的 HTTP 请求,并且位于 TagSupport 类的 pageContext 字段中。清单 12 中的 doStartTag() 方法使用 pageContext 字段访问 HttpServletRequest 对象,然后将 HttpServletRequest 对象存储在一个名为 request 的类级变量中。

下一节介绍 doStartTag() 方法如何使用 HttpServletRequest 对象。

 

3 步:使用 WURFL 处理类

doStartTag() 方法使用 WURFL 处理类了解请求客户机的首选标记,如 清单 13 所示。


清单 13. 查找请求客户机设备的首选标记

                   

public int doStartTag() throws JspException {

 

    /***Lines of code for Step 1***/

   

    /***Lines of code for Step 2***/

 

    /*** Step3 ***/ 

    try {

        cm = ObjectsManager.getCapabilityMatrixInstance();

        uam = ObjectsManager.getUAManagerInstance();

 

        //Check the capability string

        warning = TagUtil.checkCapability("preferred_markup");

        if (warning.length() > 0) {

            throw new JspException(warning);

        }

 

        //get the user agent

        UA = TagUtil.getUA(this.request);

        device_id = uam.getDeviceIDFromUALoose(UA);

        mark_up = cm.getCapabilityForDevice(device_id, "preferred_markup");

        mark_up = TagUtil.getWallMarkup(mark_up);

 

        <!--Other steps in the doStartTag() method-->

 

   } catch (Exception e){

       e.printStackTrace();

   }

 

}//doStartTag

 

可以看到 doStartTag() 方法使用 ObjectsManager 类的 getCapabilityMatrixInstance() getUAManagerInstance() 方法分别创建 CapabilityMatrix UAManager 类的实例。然后 doStartTag() 方法调用 TagUtil 类的 checkCapability() 方法。只有在 wurfl.xml 文件中已经定义了这个功能之后,checkCapability() 方法才接受功能的名称,并返回空的字符串。因此,如果 TagUtil.checkCapability() 方法返回一个空字符串,则可断定此功能已在 wurfl.xml 文件中定义。doStartTag() 方法使用 checkCapability() 来确保 preferred_markup 功能已在 wurfl.xml 文件中定义。

然后,doStartTag() 方法使用 TagUtil 类从在第 2 步中获得的 HttpServletRequest 对象中提取 user-agent 字符串。为此,doStartTag() 方法使用 TagUtil 类的 getUA() 方法。getUA() 方法将 HttpServletRequest 对象作为传入参数接受,并返回在 HTTP 请求中定义的 User-Agent 参数的值。doStartTag() 方法将 User-Agent 参数的值存储在一个名为 UA 的变量中。

现在 doStartTag() 方法使用 user-agent 字符串了解请求客户机的设备 ID。因此,doStartTag() 方法调用 UAManager 类的 getDeviceIDFromUALoose() 方法,将 UA 作为参数随方法调用一起传递。回忆 使用 WURFL Java API 了解设备的功能 一节,那一节的 清单 9 中演示了 getDeviceIDFromUALoose() 方法的使用。

doStartTag() 方法将设备 ID 存储在它的 device_id 变量中,如 清单 13 所示。现在 doStartTag() 方法调用 CapabilityMatrix 对象的 getCapabilityForDevice() 方法,将 device_id 作为第一个参数传递给方法调用。这类似于您在 清单 9 的第 6 步中所看到的内容,在这一步中介绍了如何使用 getCapabilityForDevice() 方法了解设备的功能。doStartTag() 方法使用 getCapabilityForDevice() 方法了解请求客户机的首选标记,以便它将 preferred_markup 字符串作为第二个参数传递给 getCapabilityForDevice() 方法调用。

现在 doStartTag() 方法具有指定请求客户机首选标记的字符串。doStartTag() 方法将该字符串传递给 TagUtil 类的 getWallMarkup() 方法。getWallMarkup() 是处理首选标记字符串,并说明此标记是 XHTMLWML 还是 CHTML 的实用方法。

现在 doStartTag() 方法知道它需要将什么标记发送回请求客户机。通过这里的讨论您或许能够猜测到,清单 13 中所示的第 3 步的整个练习都只是为了了解请求客户机的首选标记。

4 步解释 doStartTag() 方法如何创建首选的标记。

 

4 步:创建首选标记

清单 14 展示了 doStartTag() 方法的标记创建逻辑。


清单 14. 呈现 XHTMLWML CHTML 标记

                   

public int doStartTag() throws JspException {

 

    /***Lines of code for Step 1***/

 

    /***Lines of code for Step 2***/

 

    /***Lines of code for Step 3***/

 

    /*** Step4 ***/ 

    try {

 

     //XHTML first

     if ( mark_up.startsWith("xhtmlmp") )

     {

         //do we need to use colorized menus?

         //check that colorized=true and that the 'menu_css' tag has been produced

         WallDocument document =

            (WallDocument)pageContext.getAttribute(

               "wall-document",

               PageContext.REQUEST_SCOPE);

 

         if (document == null) {

             throw new JspTagException(

                 "tag 'menu' must be nested inside a 'document' tag");

         }

         menu_css_tag = document.getCSSMenuRequested();

  

         warning = TagUtil.checkCapability("xhtml_supports_table_for_layout");

         warning += TagUtil.checkCapability("xhtml_supports_css_cell_table_coloring");

 

         if (warning.length() > 0) {

             throw new JspException(warning);

         }

  

         table_ok = cm.getCapabilityForDevice(device_id,

                                      "xhtml_supports_table_for_layout");

         css_ok = cm.getCapabilityForDevice(device_id,

                                     "xhtml_supports_css_cell_table_coloring");

         table_and_css_background =

           (table_ok.equals("true") && css_ok.equals("true"));

 

         //if the user wants to do it fancy and the device allows for it

         if (menu_css_tag && colorize && table_and_css_background) {

             try {

                 JspWriter out = pageContext.getOut();

                 out.println("<table>");

             } catch(IOException ioe) {

                 System.out.println("Error in menu tag: " + ioe);

             }

             return(EVAL_BODY_INCLUDE);

         } else {

             //OK, just default plain menus

             try {

                 JspWriter out = pageContext.getOut();

                 out.print("<ol>");

             } catch(IOException ioe) {

                 System.out.println("Error in menu tag: " + ioe);

             }

             return(EVAL_BODY_INCLUDE);

         }

     }

 

     //CHTML

     if ( mark_up.startsWith("chtml") )

     {

         try {

             JspWriter out = pageContext.getOut();

             out.println("<br clear=/"all/">");

         } catch(IOException ioe) {

             System.out.println("Error in menu tag: " + ioe);

         }

         return(EVAL_BODY_INCLUDE);

     }

 

     //WML

     if ( mark_up.startsWith("wml") )

     {

         warning = TagUtil.checkCapability("menu_with_select_element_recommended");

         if (warning.length() > 0) {

             throw new JspException(warning);

         }  

         wml_menu_with_select =

             cm.getCapabilityForDevice(

                 device_id,

                 "menu_with_select_element_recommended");

 

         try {

             JspWriter out = pageContext.getOut();

             if (wml_menu_with_select.equals("true")) {

                out.println("<p align=/"left/" mode=/"nowrap/">");

                out.print("<select>");

             } else {

                out.print("<p>");

             }

         } catch(IOException ioe) {

             System.out.println("Error in menu tag: " + ioe);

        }

     }

 

}//doStartTag()

 

看一下 清单 14 中的 if (markup.startsWith()) 语句(以粗体显示),该语句决定 doStartTag() 方法将创建哪个标记。例如,看一下第一个 if 块,它创建 XHTML 标记(一个开始的 <table> 标记或者一个开始的 <ol> 标记)。它使用 TagUtil.checkCapability() 方法检查请求客户机是否支持 XHTML 表以及颜色以控制菜单项的显示。如果设备不支持这些功能,则 if 块创建 <table> 标记;否则,它创建 <ol> 标记。

如您所料,doStartTag() 方法只创建开始标记。结束标记的创建由 WallMenu 类的 doEndTag() 方法控制,如 清单 15 所示。


清单 15. WallMenu 类的 doEndTag() 方法

                   

public int doEndTag() {

    //only need to close menus (table or 'ol') for XHTML

    if ( mark_up.startsWith("xhtmlmp") ) {

        //if the user wants to do it fancy and the device allows for it

        if (menu_css_tag  && colorize && table_and_css_background) {

            try {

                JspWriter out = pageContext.getOut();

                out.println("</table>");

            } catch(IOException ioe) {

                System.out.println("Error in menu tag: " + ioe);

            }

       

        } else {

            //OK, just default plain menus

            try {

                JspWriter out = pageContext.getOut();

                out.println("</ol>");

            } catch(IOException ioe) {

                System.out.println("Error in menu tag: " + ioe);

            }

        }

    }  

 

    if ( mark_up.startsWith("wml") ) {

        try {

            JspWriter out = pageContext.getOut();

            if (wml_menu_with_select.equals("true")) {

                out.println("</select>");

                out.print("</p>");

            } else {

                out.print("</p>");

            }

        } catch(IOException ioe) {

            System.out.println("Error in menu tag: " + ioe);

        }

    }

 

    return(EVAL_PAGE); // Continue with rest of JSP page

}

 

可以看到简单的 doEndTag() 方法只是为在第 4 步中 doStartTag() 创建的开始标记创建特定于设备的结束标记。

本节已经介绍了 WALL 的标记句柄类的工作方式。下一节将使用在本节中学习的概念并且介绍如何构建自己的多服务 JSP 标记。

 

构建定制的多服务标记

在无线设备上显示菜肴

清单 5 介绍了名为 multiServeMenuCategories.jsp 的多服务页面,该页面显示餐馆菜单中所包含的分类。已经在 使用 WML 显示餐馆菜单 一节和 使用 XHTML 一节中看到了由 multiServeMenuCategories.jsp 页面为不同的无线设备生成的标记和屏幕截图。

当无线设备用户选择一个菜单分类时,将调用 multiServeDishes.jsp 页面。multiServeDishes.jsp 页面显示所选择的菜单种类中包含的菜品。

本系列的第 2 部分讨论从一个 JSP 页面移动到另一个 JSP 页面的机制,并且演示 JSF 如何收集用户的数据以及如何将其传递到下一个页面。现在先让我们了解一下如何开发用于显示用户所选择的菜单分类中所包含菜肴所需的定制多服务标记。

multiServeDishes.jsp 页面显示很多菜肴的下列信息:

  • 菜名
  • 价格
  • 简短描述

multiServeDishes.jsp 页面需要构建两个名为 products product 的定制多服务标记。<product> 标记代表特定的菜,<products> 标记包含很多 <product> 标记。

注意到,两个定制的标记分别命名为 products product 而不是 dishes dish。这就是为什么要构建可重用的(因此,也是一般的)多服务标记的原因,您将能够在自己的应用程序而不是示例餐馆的菜单应用程序中使用这些标记。

清单 16 显示 multiServeDishes.jsp 页面的 JSP 代码。


清单 16. multiServeDishes.jsp 页面

                   

<%@ taglib uri="/WEB-INF/tld/wall.tld" prefix="wall" %>

<wall:document>

   <wall:xmlpidtd />

 

   <wall:head>

      <wall:title>Starters</wall:title>

      <wall:menu_css/>

   </wall:head>

 

   <wall:body>

       <wall:products title="Starter"

           instructionForUser="Select one or more dishes:"

           submissionLabel="AddToCart"

           submissionURL="http://www.AFictitiousRestaurant.com">

           <wall:product name="Butterfly prawns"

               price="5"

               currency="USD"

               description="Prawns with butter"

               selected="selected"/>

           <wall:product name="Chicken tempura"

               price="3"

               currency="USD"

               description="Chicken filet"/>

           <wall:product name="Prawn toast"

               price="2"

               currency="USD"

               description="Prawns with bread"/>

       </wall:products>

    </wall:body>

</wall:document>

 

注意到,multiServeDishes.jsp 页面包含一个 <products> 标记和三个 <product> 标记。<products> 标记代表三道菜。每道菜都包含在 <product> 标记中。

本节的其余部分介绍 清单 16 multiServeDishes.jsp 页面中的 <products> <product> 标记的工作方式。

 

multiServeDishes.jsp 的标记和屏幕截图

出于演示的目的,本教程使用两个版本(5.1 7.0)的 Phone Simulator 模拟器工具。这两个版本都可以免费下载(请参阅 参考资料)。Phone Simulator 5.1 的首选标记为 WMLPhone Simulator 7.0 的首选标记为 XHTML清单 17 显示 multiServeDishes.jsp 页面生成的 XHTML 标记。


清单 17. multiServeDishes.jsp 生成的 XHMTL 标记

                   

<?xml version="1.0"?>

<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.0//EN"

    "http://www.wapforum.org/DTD/xhtml-mobile10.dtd">

 

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">

<head>

   <title>Starters</title>

   <style>

       .bgcolor1 { background-color:#99CCFF;}

       .bgcolor2 { background-color:#FFFFFF;}

   </style>

 

</head>

<body>

    <form action="http://www.AFictitiousRestaurant.com" method="post">

        <p>

            Select one or more dishes:

            <select name="starter" multiple="true">

                <option value="Butterfly prawns" class="bgcolor1" selected="selected">

                    Butterfly prawns (USD:5)<br/>Prawns with butter

                </option>

                <option value="Chicken tempura" class="bgcolor2">

                    Chicken tempura (USD:3)<br/>Chicken filet

                </option>

                <option value="Prawn toast" class="bgcolor1">

                    Prawn toast (USD:2)<br/>Prawns with bread

                </option>

            </select>

            <br/>

            <input type="submit" value="AddToCart" />

        </p>

    </form>

</body>

</html>

 

注意到 <select> 标记具有一个 multiple="true" 属性,该属性表示用户可以选择设备屏幕上显示的多个选项。XHTML 标记还包含三个 <option> 标记,它们包含在 <select> 标记中。每个 <option> 标记代表一道特定的菜肴。

7 显示 清单 17 出现在 Phone Simulator 7.0 屏幕上的方式,还显示相同的菜。


7. 显示 XHTML 标记的 Phone Simulator 7.0

清单 18 显示 multiServeDishes.jsp Phone Simulator 5.1 客户机生成的 WML 标记。


清单 18. multiServeDishes.jsp WML 标记

                   

<?xml version="1.0"?>

<!DOCTYPE wml PUBLIC "-//PHONE.COM//DTD WML 1.1//EN"

        "http://www.phone.com/dtd/wml11.dtd" >

<wml>

    <head>

       <meta name="taglib" content="WALL" />

    </head>

 

    <card id="w" title="Starters">

        <p>

          Select one or more dishes:

          <select name="starter" multiple="true">

              <option value="Butterfly prawns">

                  Butterfly prawns (USD:5) Prawns with butter

              </option>

              <option value="Chicken tempura">

                  Chicken tempura (USD:3) Chicken filet

              </option>

              <option value="Prawn toast">

                  Prawn toast (USD:2) Prawns with bread

              </option>

          </select>

          <br/>

          <anchor>AddToCart

              <go href="http://www.AFictitiousRestaurant.com" method="post">

                 <postfield name="starter" value="$starter" />

              </go>

          </anchor>

       </p>

    </card>

</wml>

 

将清单 17 18 进行对比以查看为两个设备所生成的 XHTML WML 标记之间的差别。清单 17 中的 XHTML <select> 标记包含在 <p> 标记中,反过来 <p> 标记又包含在 <form> 标记中。另一方面,清单 18 中的 WML <select> 标记 包含在 <p> 标记中,而 <p> 标记又包含在 WML <card> 标记中。 8 展示了 Phone Simulator 5.1 显示 清单 18 WML 标记的方式。


8. 显示 WML 标记的 Phone Simulator 5.1

<products> <product> 标记的标记句柄类分别处理清单 17 18 中显示的多服务的 XHTML WML 标记的创建。现在,让我们看一看如何为 <products> <product> 标记开发标记句柄类。

 

实现 <products> 标记的句柄

WallProducts 类是 <products> 标记的标记句柄类,它负责处理以下 XHTML WML 标记的创建:

  • 清单 17 <form><p> <select> XHTML 标记(如果请求客户机的首选标记为 XHTML
  • 清单 18 <p><select><anchor><go> <postfield> WML 标记(如果请求客户机的首选标记为 WML

清单 17 18 中显示的 <option> 标记由 <product> 标记的句柄创建,将在后面的 创建用于显示产品的标记 一节中进行介绍。

清单 19 显示 WallProducts 类的 doStartTag() 方法实现。


清单 19. WallProducts 类的 doStartTag() 方法

                    

public int doStartTag()

    throws JspException

{

    /***Lines of code for Step 1***/

 

    /***Lines of code for Step 2***/

 

    try

    {

        /***Lines of code for Step 3***/

 

        /*** Step 4 ***/

        if (markUp.startsWith("xhtmlmp"))

        {

            try

            {

                JspWriter jspwriter = pageContext.getOut();

                jspwriter.println("<form action=/"" + submissionURL +

                    "/" method=/"post/">");

                jspwriter.println("<p>");

                jspwriter.println( instructionForUser );

                jspwriter.println("<select name=/"" + title + "/" multiple=/"true/">");

                return EVAL_BODY_INCLUDE;       

            }

            catch(IOException ie)  {

                System.out.println("Error in xhtml structure of Products tag: " + ie);

            } 

        }

        else if (markUp.startsWith("wml"))

        {

            try

            {

                JspWriter jspwriter = pageContext.getOut();

                jspwriter.println("<p>");

                jspwriter.println( instructionForUser );

                jspwriter.println("<select name=/"" + title +

                    "/" multiple=/"true/">");     

                return EVAL_BODY_INCLUDE;

            }

            catch(IOException ie) {

                System.out.println("Error in wml structure of Products tag: " + ie);

            }

        }

    } catch (Exception e) {

        System.out.println("Error in WallProducts tag : " + e.getMessage());

        System.out.println("Error: " + e.toString());

    }

    return SKIP_BODY;

 

}//doStartTag

 

注意到,前三步与前面的清单 1112 13 中的前三步相同。因此,清单 19 只显示第四步的代码。

前三步的结果是 doStartTag() 方法知道了请求客户机的首选标记。在第四步中,只根据客户机的首选标记创建 XHTML WML 标记。下一节介绍创建的方法。

 

创建产品分类的标记

清单 19 显示 WallProducts 类的 doStartTag() 方法的第四步。doStartTag() 方法为 XHTML WML 目标设备创建多服务标记。如果发现请求客户机的首选标记为 XHTML,则 doStartTag() 方法创建 清单 20 中所示的下列 XHTML 标记:


清单 20. WallProducts.doStartTag() 创建的 XHTML 标记

                   

<form action="http://www.AFictitiousRestaurant.com" method="post">

    <p>

       Select one or more dishes:

       <select name="starter" multiple="true">

 

注意到 doStartTag() 方法创建的 XHTML 标记包含一些固定的部分,无论什么时候调用 doStartTag() 这些部分总是保持相同,和根据传递给 JSP 页面的 <products> 标记的属性自动创建的一些部分一样(例如,清单 16 中的 <products> 标记的 submissionURL 属性)。 清单 20 以粗体显示了所有自动创建的部分。

如果 doStartTag() 方法发现请求客户机的首选标记为 WML,则它创建 清单 21 中所示的标记。


清单 21. WallProducts.doStartTag() 创建的 WML 标记

                   

<p>

    Select one or more dishes:

    <select name="starter" multiple="true">

 

从清单 20 21 中可以看到 doStartTag() 只创建 <form><p> <select> XHTML 标记以及 <p> <select> WML 标记的开始标记。它并不创建任何结束标记,结束标记由 清单 22 中所示的 WallProducts.doEndTag() 方法创建。


清单 22. WallProducts 类的 doEndTag() 方法

                   

public int doEndTag()

{

    if (markUp.startsWith("xhtmlmp"))

    {

        try

        {

            JspWriter jspwriter = pageContext.getOut();

            jspwriter.println("</select>");

            jspwriter.println("<br/>");

            jspwriter.println("<input type=/"submit/" value=/"" +

                submissionLabel+ "/" />");                

            jspwriter.println("</p>");

            jspwriter.println("</form>");

        }

        catch(IOException ie)

        {

            System.out.println("Error in products end tag: " + ie);

        }

    }

    else if (markUp.startsWith("wml"))

    {

        try

        {

            JspWriter jspwriter = pageContext.getOut();

            jspwriter.println("</select>");

            jspwriter.println("<br/>");

            jspwriter.println("<anchor>"+submissionLabel);

            jspwriter.println("<go href=/""+submissionURL+"/" method=/"post/">");

            jspwriter.println("<postfield name=/""+ title +"/" value=/"$"+title+"/" />");

            jspwriter.println("</go>");

            jspwriter.println("</anchor>");                   

            jspwriter.println("</p>");

        }

        catch(IOException ie)

        {

            System.out.println("Error in products end tag: " + ie);

        }

    }

 

    return EVAL_PAGE;

 

}//doEndTag

 

doEndTag() 创建结束标记之后,清单 20 21 中的 XHTML WML 标记分别类似于清单 23 24 中的 XHTML WML 标记。


清单 23. WallProducts 创建的 XHTML 标记

                   

<form action="http://www.AFictitiousRestaurant.com" method="post">

    <p>

       Select one or more dishes:

       <select name="starter" multiple="true">

          /***A number of product tags will insert their markup here***/

       </select>

 

       <br/>

       <input type="submit" value="AddToCart" />

    </p>

</form>



清单 24. WallProducts 创建的 WML 标记

                   

<p>

    Select one or more dishes:

    <select name="starter" multiple="true">

       /***A number of product tags will insert their markup here***/

    </select>

 

    <br/>

    <anchor>AddToCart

        <go href="http://www.AFictitiousRestaurant.com" method="post">

            <postfield name="starter" value="$starter" />

        </go>

    </anchor>

</p>

 

清单 23 24 包括在将插入很多 <product> 标记的位置标记的很多注释。下一节介绍 WallProduct 类(<product> 标记的句柄)如何创建它的标记。

 

创建用于显示产品的标记

现在,让我们看一看 WallProduct 类的 doStartTag() doEndTag() 方法的工作方式。清单 25 显示 WallProduct.doStartTag() 方法的代码。


清单 25. WallProduct.doStartTag() 方法

                    

public int doStartTag() throws JspException

{

    /*** Step 1 ***/

    WallProducts products =

        (WallProducts)TagSupport.findAncestorWithClass(

            this,

            net.sourceforge.wurfl.wall.WallProducts.class);

 

    if(products == null)

        throw new JspTagException (

            "'product' tag must be nested inside a 'products' tag");

 

    /*** Step 2 ***/

    markUp = products.getPreferredMarkUp();

 

    /*** Step 3 ***/

    WallDocument walldocument =

        (WallDocument)pageContext.getAttribute(

            "wall-document", PageContext.REQUEST_SCOPE);

 

    if(walldocument == null)

        throw new JspTagException ("'document' object is null ");

 

    /*** Step 4 ***/

    try {

        capabilityMatrix = ObjectsManager.getCapabilityMatrixInstance();

        deviceID = products.getDeviceID();

 

        if (markUp.startsWith("xhtmlmp") )

        {

            css_menu_enabled = walldocument.getCSSMenuRequested();

            css_format_enabled =

                capabilityMatrix.getCapabilityForDevice(

                    deviceID,

                    "xhtml_format_as_css_property");

            css_menu_and_format_enabled =

                (css_menu_enabled && css_format_enabled.equals("true"));

 

            try

            {

                JspWriter jspWriter = pageContext.getOut();

                jspWriter.print("  <option value=/""+name+"/"");

 

                if ( css_menu_and_format_enabled )

                    jspWriter.print(" class=/""+products.getBGColor()+"/"");

                                

                if (selected.equals("selected"))

                    jspWriter.print(" selected=/"selected/">");

                else

                    jspWriter.print(">");               

                     

                jspWriter.println( name+" ("+currency+":"+price+")");

                jspWriter.print("<br/>");

                jspWriter.print(description);

                return EVAL_BODY_INCLUDE;

            }

            catch(IOException ie)

            {

                System.out.println("Error in xhtml structure of 'product' tag: " + ie);

            } 

        }

 

        /*** Step 5 ***/

        else if (markUp.startsWith("wml"))

        {

            try

            {

                JspWriter jspWriter = pageContext.getOut();

                jspWriter.print("  <option value=/""+name+"/">");

                jspWriter.println( name+" ("+currency+":"+price+") ");

                jspWriter.print(description);

                return EVAL_BODY_INCLUDE;

            }

            catch(IOException ie)

            {

                System.out.println("Error in wml structure of 'product' tag: " + ie);

            } 

        }

    } catch(Exception e)   {

        System.out.println("Error in <Product> tag " + e);

    }

    return SKIP_BODY;

 

}//doStartTag

 

清单 25 包含以下步骤:

  1. doStartTag() 方法获得 WallProducts 标记句柄类的引用,这个类是在前面的几节中创建的。WallProducts WallProduct 的祖先对象,因此在 清单 25 的第一步中,使用在前面的 使用 TagSupport 一节中看到的 findAncestorWithClass() 方法。
  2. 现在 doStartTag() 使用 WallProducts 对象了解请求客户机的首选标记。注意,不需要浏览使用 WURFL 了解首选标记的整个过程。WallProducts 祖先对象已经通过 实现 <products> 标记的句柄 一节中 介绍的 doStartTag() 方法的前三步中知道了首选标记。可以调用 WallProducts 类的 getPreferredMarkUp() 方法以了解首选标记。
  3. doStartTag() 方法获取 WallDocument 对象,该对象是 清单 25 中的 <wall:document> 标记的句柄。WallDocument 类具有一个名为 getCSSMenuRequested() 的方法,该方法告知 JSP 页面是否包含 <wall:menu_css> 标记。doStartTag() 将在后面的第 4 步中调用 getCSSMenuRequested() 方法。
  4. 现在 doStartTag() 检查请求客户机的首选标记是否为 XHTML。如果是 XHTML,则 doStartTag() 创建 清单 26 中所示的 XHTML 标记。

    清单 26. WallProduct.doStartTag() 创建的 XHTML 标记

                           

<option value="Butterfly prawns" class="bgcolor1" selected="selected">

    Butterfly prawns (USD:5)<br/>Prawns with butter


注意到,粗体的部分(class="bgcolor1"),该部分为每个产品显示彩色背景。仅在以下情况下才能使用彩色背景:

    1. WallDocument.getCSSMenuRequested() 方法的调用返回 true,这意味着 JSP 页面包含 <wall:menu_css> 标记
    2. 名为 xhtml_format_as_css_property WURFL 功能被启用,这意味着请求客户机能够显示样式。
  1. 最后,doStartTag() 检查请求客户机的首选标记是否为 WML。如果是 WML,则 doStartTag() 创建 清单 27 中所示的标记。

    清单 27. WallProduct.doStartTag() 创建的 WML 标记

                           

<option value="Butterfly prawns">

    Butterfly prawns (USD:5) Prawns with butter

从清单 26 27 中可以看出,由 WallProduct.doStartTag() 所生成的 XHTML WML 代码都创建 <option> 标记。这意味着由 WallProduct.doEndTag() 创建结束标记是非常简单的。WallProduct.doEndTag() 只需要创建 </option> 结束标记,如 清单 28 所示。
清单 28. WallProduct.doEndTag() 方法

                           

public int doEndTag() throws JspException

{

    try {

        JspWriter jspWriter = pageContext.getOut();

        jspWriter.print("</option>");

    } catch(IOException ie) {

        System.out.println("Error in end <Product> tag: " + ie.getMessage());

    }   

    return EVAL_PAGE;

 

}//doEndTag

 

如何试用 mutliServeDishes.jsp

现在应该试用一下本节中开发的 multiServeDishes.jsp 页面了。首先,编译 WallProducts WallProduct 类。可以使用以下命令行语句编译这两个类:

javac -classpath .;x:/wurfl/WEB-INF/lib/wurfltags.jar; WallProducts.Java

 

javac -classpath .;x:/wurfl/WEB-INF/lib/wurfltags.jar; WallProduct.java

 

这些命令行语句根据 WALL 的包名称 (net.sourceforge.wurfl.wall) 在文件夹层次 (net/sourceforge/wurfl/wall) 中生成已编译好的这两个类。

下面,将已编译好的两个类放到名为 wurfltags.jar WURFL JAR 文件中,可以从 WURFL 的官方网站下载这个文件。(请参阅 参考资料)。以下命令行语句将两个类放到 JAR 文件中:

jar -uvf wurfltags.jar *

 

注意,运行 JAR 命令时,WallProducts WallProduct 类应该位于 WALL 的文件夹层次结构(net/sourceforge/wurfl/wall)中。

本教程使用 Apache Tomcat 试用 multiServeDishes.jsp 页面。可以通过将更新的 JAR 文件复制到安装 Tomcat x:/tomcat-5.x/common/lib 文件夹中在 Tomcat 中试用页面。

还需要更新 WALL TLD 文件 wall.tld。更新后的 TLD 文件包含本节中开发的 <products> <product> 标记的条目。

在安装 Tomcat webapps 文件夹中创建一个名为 MultiServeRestaurantMenu 的新的 Tomcat 项目文件夹。然后在 MultiServeRestaurantMenu 文件夹中创建一个名为 WEB-INF/tld 的子文件夹,并且将更新的 wall.tld 文件复制到 MultiServeRestaurantMenu/WEB-INF/tld 文件夹中。还要将 multiServeMenuCategories.jsp multiServeDishes.jsp 文件复制到 MultiServeRestaurantMenu 文件夹中。

运行 Tomcat 和无线客户机浏览器(Phone Simulator 5.1 7.0)。将浏览器指向以下 URL 以演示多服务:

http://localhost:8080/MultiServeRestaurantMenu/multiServeDishes.jsp

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值