2007年4月12日

          Mondrian是一個(gè)開放源代碼的Rolap服務(wù)器,使用java開發(fā)的。它實(shí)現(xiàn)了xmla和jolap規(guī)范,而且自定義了一種使用mdx語言的客戶端接口。Mondrian是olap服務(wù)器,而不是數(shù)據(jù)倉庫服務(wù)器,因此Mondrian的元數(shù)據(jù)主要包括olap建模的元數(shù)據(jù),不包括從外部數(shù)據(jù)源到數(shù)據(jù)庫轉(zhuǎn)換的元數(shù)據(jù)。也就是說Mondria的元數(shù)據(jù)僅僅包括了多維邏輯模型,從關(guān)系型數(shù)據(jù)庫到多維邏輯模型的映射,存取權(quán)限等信息。在功能上,Mondrian支持共享維和成員計(jì)算,支持星型模型和雪花模型的功能。
          Mondrian中使用物理的xml文件存儲(chǔ)元數(shù)據(jù),它的設(shè)計(jì)者規(guī)定了xml文件的格式。下面簡單介紹一下它是如何存儲(chǔ)元數(shù)據(jù)的。

          Element Description
          根元素
          <Schema> Collection of Cubes, Virtual cubes, Shared dimensions, and Roles.
          邏輯元素
          <Cube> A collection of dimensions and measures, all centered on a fact table.
          <VirtualCube> A cube defined by combining the dimensions and measures of one or more cubes.
          <Dimension>
          <DimensionUsage> Usage of a shared dimension by a cube.
          <Hierarchy>
          <Level>
          <Property>
          <Measure>
          物理元素
          <Table> Fact- or dimension table.
          <View> Defines a 'table' using a SQL query, which can have different variants for different underlying databases.
          <Join> Defines a 'table' by joining a set of queries.
          存取控制
          <Role> An access-control profile.
          <SchemaGrant> A set of rights to a schema.
          <CubeGrant> A set of rights to a cube.
          <HierarchyGrant> A set of rights to a hierarchy and levels within that hierarchy.
          <MemberGrant> A set of rights to a member and its children.
          其他
          <Parameter>
          <Table>
          <Table>



          一個(gè)模式定義一個(gè)多維數(shù)據(jù)庫,它包括一個(gè)邏輯模型,由立方體,層次,成員和邏輯模型到物理模型的映射構(gòu)成。一個(gè)邏輯模型由可以用MDX語言來查詢。Mondrain的模型由xml文件來描述。現(xiàn)在創(chuàng)建模式的唯一途徑是用文本編輯a器編輯xml文件。Xml的語法不是太復(fù)雜,因此沒有想象中的那么難。目前正在開發(fā)一個(gè)圖形界面的程序來創(chuàng)建和修改模式。
          一個(gè)模式最重要的組成部分是立方體,度量和維:在一個(gè)主題域中立方體是維和度量的集合。一個(gè)度量是一個(gè)可測量的數(shù)值,比如產(chǎn)品銷售的數(shù)量或者詳細(xì)清單的價(jià)格
          一個(gè)維是一個(gè)屬性或者是屬性的集合, 通過維你可以將度量劃分到字類中。比如:你希望將銷售產(chǎn)品按顏色,顧客性別,售出的商店分為八個(gè)部分,那么顏色,性別,商店都是維。

          下面是一個(gè)簡單的模型定義的例子:
          <Schema>
          <Cube name="Sales">
          <Table name="sales_fact_1997"/>
          <Dimension name="Gender" foreignKey="customer_id">
          <Hierarchy hasAll="true" allMemberName="All Genders" primaryKey="customer_id">
          <Table name="customer"/>
          <Level name="Gender" column="gender" uniqueMembers="true"/>
          </Hierarchy>
          </Dimension>
          <Dimension name="Time" foreignKey="time_id">
          <Hierarchy hasAll="false" primaryKey="time_id">
          <Table name="time_by_day"/>
          <Level name="Year" column="the_year" type="Numeric"
          uniqueMembers="true"/>
          <Level name="Quarter" column="quarter"
          uniqueMembers="false"/>
          <Level name="Month" column="month_of_year" type="Numeric"
          uniqueMembers="false"/>
          </Hierarchy>
          </Dimension>
          <Measure name="Unit Sales" column="unit_sales"
          aggregator="sum" formatString="#,###"/>
          <Measure name="Store Sales" column="store_sales"
          aggregator="sum" formatString="#,###.##"/>
          </Cube>
          </Schema>

          這個(gè)模型包含了一個(gè)銷售cube,這個(gè)cube有兩個(gè)維,時(shí)間和性別維;兩個(gè)度量,銷售數(shù)量和銷售總額。
          我們可以在這個(gè)模型上寫一個(gè) MDX 查詢:
          select {[Measures].[Unit Sales], [Measures].[Store Sales]} on columns,
          {[Time].[1997].[Q1].descendants} on rows
          from [Sales]
          where [Gender].[F]
          這 個(gè)查詢涉及到了銷售立方體, 每一個(gè)維 [Measures], [Time], [Gender], 這些維的多個(gè)成員. 結(jié)果如下:
          [Time] [Measures].[Unit Sales] [Measures].[Store Sales]
          [1997].[Q1] 0 0
          [1997].[Q1].[Jan] 0 0
          [1997].[Q1].[Feb] 0 0
          [1997].[Q1].[Mar] 0 0

          下面詳細(xì)地介紹一下模式定義:
          一個(gè)立方體是一個(gè)或者多個(gè)維和度量的集合,通常是一個(gè)事實(shí)表,這里是 ‘sales_fact_1997". 事實(shí)表保存了需要計(jì)算的列和包含維的參考表.
          <Cube name="Sales">
          <Table name="sales_fact_1997"/>
          ...
          </Cube>
          這里用 <Table> 元素定義事實(shí)表. 如果事實(shí)表 不在默認(rèn)的模式中, 你可以用"schema"屬性指定一個(gè)明確地模式,例如:
          <Table schema="foodmart" name="sales_fact_1997"/>
          你也可以利用 <View> 和 <Join> 結(jié)構(gòu)來創(chuàng)建更復(fù)雜的sql .
          度量
          銷售立方體定義了兩個(gè)維 "Unit Sales" 和 "Store Sales".
          <Measure name="Unit Sales" column="unit_sales"
          aggregator="sum" formatString="#,###"/>
          <Measure name="Store Sales" column="store_sales"
          aggregator="sum" formatString="#,###.00"/>
          每個(gè)度量有一個(gè)名字,對(duì)應(yīng)事實(shí)表中的一列, 采用一個(gè)聚集函數(shù) (usually "sum").
          一個(gè)可選的格式字符串指定了值如何被打印. 這里我們選擇銷售數(shù)量不帶小數(shù)的輸出(因?yàn)殇N售數(shù)量是整數(shù)) ,銷售總額帶2位小數(shù) . 符號(hào)',' 和 '.' 是對(duì)地區(qū)敏感的, 因此如果是在意大利運(yùn)行, 銷售總額可能會(huì)出現(xiàn) "48.123,45". 你可以用 advanced format strings來實(shí)現(xiàn)更嚴(yán)格的效果.度量值不是從列中來的,而是從立方體的單元中來的

          性別維由單一的層次組成,僅有一層。
          <Dimension name="Gender" foreignKey="customer_id">
          <Hierarchy hasAll="true" primaryKey="customer_id">
          <Table name="customer"/>
          <Level name="Gender" column="gender" uniqueMembers="true"/>
          </Hierarchy>
          </Dimension>
          對(duì)于任意給定的銷售, 性別維是指購買改產(chǎn)品的客戶的性別. 它通過連接事實(shí)表"sales_fact_1997.customer_id"和維表"customer.customer_id"
          來表示 。"gender" 包括兩個(gè)值, 'F' 和 'M', 因此性別維包含的成員: [Gender].[F] and [Gender].[M]. 因?yàn)?hasAll="true", 系統(tǒng)產(chǎn)生一個(gè)特別的 'all' 層, 僅包括一個(gè)成員 [All Genders].
          一個(gè)維可以包含多個(gè)層次:
          <Dimension name="Time" foreignKey="time_id">
          <Hierarchy hasAll="false" primaryKey="time_id">
          <Table name="time_by_day"/>
          <Level name="Year" column="the_year" type="Numeric"
          uniqueMembers="true"/>
          <Level name="Quarter" column="quarter"
          uniqueMembers="false"/>
          <Level name="Month" column="month_of_year" type="Numeric"
          uniqueMembers="false"/>
          </Hierarchy>
          <Hierarchy name="Time Weekly" hasAll="false" primaryKey="time_id">
          <Table name="time_by_week"/>
          <Level name="Year" column="the_year" type="Numeric"
          uniqueMembers="true"/>
          <Level name="Week" column="week"
          uniqueMembers="false"/>
          <Level name="Day" column="day_of_week" type="String"
          uniqueMembers="false"/>
          </Hierarchy>
          </Dimension>
          第一個(gè)層次沒有指定名稱.缺省的情況下,一個(gè)層次擁有和它的維相同的名稱。,因此第一個(gè)層次成為"Time".這些層次沒有太多的共同之處,他們甚至沒有相同的表,除非它們連接了實(shí)施表中的同一列"time_id"。在一個(gè)維上存在兩個(gè)層次的原因是這樣對(duì)最終用戶是有用的. 如果一個(gè)維上存在兩個(gè)層次, MDX會(huì)強(qiáng)制不允許在一個(gè)查詢中同時(shí)用到他們.
          A dimension can live in the fact table:
          <Cube name="Sales">
          <Table name="sales_fact_1997"/>
          ...
          <Dimension name="Payment method">
          <Hierarchy hasAll="true">
          <Level name="Payment method" column="payment_method" uniqueMembers="true"/>
          </Hierarchy>
          </Dimension>
          </Cube>
          每個(gè)維包含有多層組成的一個(gè)層次,

          大多數(shù)維都是僅有一個(gè)層次,但有時(shí)候一個(gè)維有多個(gè)層次。比如:你可能希望在時(shí)間維上從天聚集到月,季度和年;或者從天聚集到周和年。這兩種層次都是從天到年,但是聚集的路徑不同。大多數(shù)層次有全成員,全成員包括層次的所有成員,因此能夠代表他們的總合。它通常命名為'All something',比如:'All stores'.




          星型模式和雪花模式
          mondrian支持星型模式和雪花模式。下面介紹一下雪花模式的建模,它需要用到操作符 <Join>.比如:
          <Cube name="Sales">
          ...
          <Dimension name="Product" foreignKey="product_id">
          <Hierarchy hasAll="true" primaryKey="product_id" primaryKeyTable="product">
          <Join leftKey="product_class_id" rightAlias="product_class" rightKey="product_class_id">
          <Table name="product"/>
          <Join leftKey="product_type_id" rightKey="product_type_id">
          <Table name="product_class"/>
          <Table name="product_type"/>
          </Join>
          </Join>
          ...
          </Hierarchy>
          </Dimension>
          </Cube>
          這里定義一個(gè) "Product" 維 由三個(gè)表構(gòu)成. 事實(shí)表連接 表"product" (通過外鍵 "product_id"),表"product"連接表"product_class" (通過外鍵 "product_class_id"),表"product_class"連接表 "product_type" (通過外鍵 "product_type_id"). 我們利用 <Join> 元素的循環(huán)嵌套, <Join>帶有兩個(gè)操作對(duì)象; 操作對(duì)象可能是表,連接或者查詢 。
          按照操作對(duì)象行的數(shù)目來安排次序,表 "product" 的行數(shù)最大, 因此它首先出現(xiàn)連接事實(shí)表;然后是表 "product_class"和 "product_type",在雪花的末端擁有的行數(shù)最小.
          注意外部元素 <Join>有一個(gè)屬性 rightAlias. 這是必要的,因?yàn)閖oin 的右邊(是內(nèi)部元素 <Join> ) 有可能是許多表組成的.這種情況下不需要屬性leftAlias,因?yàn)榱?leftKey 很明確的來自表 "product".

          共享維
          當(dāng)為一個(gè)連接生成SQL的時(shí)候, mondrian 需要知道連接哪一個(gè)列. 如果一正在連接一個(gè)多表連接, 你需要告訴它連接這些表里的哪一個(gè)表,哪一個(gè)列.
          因?yàn)楣蚕砭S不屬于一個(gè)cube,你必須給它們一個(gè)明確的表 (或者數(shù)據(jù)源). 當(dāng)你在一個(gè)特別的cube里用他們的時(shí)候, 你要指定外鍵 foreign key. 下面的例子顯示了 Store Type 維被 連接到 Sales cube ,用了外鍵 sales_fact_1997.store_id, 并且被連接到Warehouse cube ,用了外鍵 warehouse.warehouse_store_id :
          <Dimension name="Store Type">
          <Hierarchy hasAll="true" primaryKey="store_id">
          <Table name="store"/>
          <Level name="Store Type" column="store_type" uniqueMembers="true"/>
          </Hierarchy>
          </Dimension>

          <Cube name="Sales">
          <Table name="sales_fact_1997"/>
          ...
          <DimensionUsage name="Store Type" source="Store Type" foreignKey="store_id"/>
          </Cube>

          <Cube name="Warehouse">
          <Table name="warehouse"/>
          ...
          <DimensionUsage name="Store Type" source="Store Type" foreignKey="warehouse_store_id"/>
          </Cube>




          虛擬 cubes
          父子層次
          一個(gè)使用方便的層次 有一個(gè)嚴(yán)格的層的集合, 成員與層緊密的聯(lián)系.比如,在 Product 層次中, 任何產(chǎn)品名稱層的成員在商標(biāo)層上都有一個(gè)父親 ,商標(biāo)層上的成員在產(chǎn)品子目錄層也都有一個(gè)父親. 這種結(jié)構(gòu)對(duì)于現(xiàn)實(shí)世界中的數(shù)據(jù)有時(shí)候太嚴(yán)格了.
          一個(gè)父子層次只有一層 (不計(jì)算 'all' 層), 但是任何成員可以在同一層上有父親成員. 一個(gè)典型的例子是Employees 層次:
          <Dimension name="Employees" foreignKey="employee_id">
          <Hierarchy hasAll="true" allMemberName="All Employees" primaryKey="employee_id">
          <Table name="employee"/>
          <Level name="Employee Id" uniqueMembers="true" type="Numeric"
          column="employee_id" nameColumn="full_name"
          parentColumn="supervisor_id" nullParentValue="0">
          <Property name="Marital Status" column="marital_status"/>
          <Property name="Position Title" column="position_title"/>
          <Property name="Gender" column="gender"/>
          <Property name="Salary" column="salary"/>
          <Property name="Education Level" column="education_level"/>
          <Property name="Management Role" column="management_role"/>
          </Level>
          </Hierarchy>
          </Dimension>
          這里parentColumn 和nullParentValue是重要的屬性:
          屬性parentColumn 是一個(gè)成員連接到它父親成員的列名。在這種情況下, 它是指向雇員經(jīng)理的外鍵。元素<Level>的子元素 <ParentExpression> 是與屬性 parentColumn 有相同作用的,但是元素允許定義任意的SQL表達(dá)式, 就像元素 <Expression>. 屬性 parentColumn (或者 元素<ParentExpression>) 是維一向Mondrian指出 層次有父子結(jié)構(gòu)的。
          屬性 nullParentValue 是指明成員沒有父成員的值 。 缺省情況下 nullParentValue="null", 但是因?yàn)樵S多數(shù)據(jù)庫不支持null, 建模時(shí) 用其他值來代替空值,0和-1.

          物理結(jié)構(gòu)
          member reade
          member reader 是訪問成員的方法. 層次通常以維表為基礎(chǔ)建立的 , 因此要用sql來構(gòu)造.但是甚至你的數(shù)據(jù)沒有存在于 RDBMS, 你可以通過一個(gè) Java 類來訪問層次。(自定義 member reader)
          Here are a couple of examples:
          DateSource (to be written)生成一個(gè)時(shí)間層次. 按常規(guī),數(shù)據(jù)倉庫工具生成一個(gè)表 ,每天包含一行。但是問題是這個(gè)表需要裝載,并且隨著時(shí)間的變化能夠添加更多的行。 DateSource 在內(nèi)存中按照要求生成日期成員.
          FileSystemSource (to be written) 按照目錄和文件的層次描述文件系統(tǒng)。 Like the time hierarchy created by DateSource, this is a virtual hierarchy: the member for a particular file is only created when, and if, that file's parent directory is expanded.
          ExpressionMemberReader (to be written) 創(chuàng)建了一個(gè)基于表達(dá)式的層次。
          自定義member reader 必須實(shí)現(xiàn)接口 mondrian.rolap.MemberSource. 如果你需要實(shí)現(xiàn)一個(gè)更大的成員操作集合, 需要實(shí)現(xiàn)接口 interface mondrian.rolap.MemberReader; 否則, Mondrian在 mondrian.rolap.CacheMemberReader中封裝 你的 reader類.你的 member reader 必須有一個(gè)公共的構(gòu)造函數(shù),這個(gè)構(gòu)造函數(shù)擁有參數(shù)(Hierarchy,Properties),拋出未檢查的錯(cuò)誤.
          Member readers 用 元素<Hierarchy> 的屬性memberReaderClass來聲明; 任何 <Parameter> 子元素通過屬性構(gòu)造函數(shù)來傳遞.
          這是一個(gè)例子:
          <Dimension name="Has bought dairy">
          <Hierarchy hasAll="true" memberReaderClass="mondrian.rolap.HasBoughtDairySource">
          <Level name="Has bought dairy" uniqueMembers="true"/>
          <Parameter name="expression" value="not used"/>
          </Hierarchy>
          </Dimension>
          Cell readers
          <Measure name="name" cellReaderClass="com.foo.MyCellReader">
          類 "com.foo.MyCellReader" 實(shí)現(xiàn)了接口interface mondrian.olap.CellReader.


          存取控制
          可以定義存取控制的屬性(角色), 作為模式的一部分, 并且可以在建立連接的時(shí)候設(shè)置角色。
          定義角色
          角色可以通過 元素<Role>來設(shè)置 , 它是元素<Schema> 的直接的子元素.
          下面是一個(gè)關(guān)于角色的例子:
          <Role name="California manager">
          <SchemaGrant access="none">
          <CubeGrant cube="Sales" access="all">
          <HierarchyGrant hierarchy="[Store]" access="custom" topLevel="[Store].[Store Country]">
          <MemberGrant member="[Store].[USA].[CA]" access="all"/>
          <MemberGrant member="[Store].[USA].[CA].[Los Angeles]" access="none"/>
          </HierarchyGrant>
          <HierarchyGrant hierarchy="[Customers]" access="custom" topLevel="[Customers].[State Province]" bottomLevel="[Customers].[City]">
          <MemberGrant member="[Customers].[USA].[CA]" access="all"/>
          <MemberGrant member="[Customers].[USA].[CA].[Los Angeles]" access="none"/>
          </HierarchyGrant>
          <HierarchyGrant hierarchy="[Gender]" access="none"/>
          </CubeGrant>
          </SchemaGrant>
          </Role>
          元素 <SchemaGrant> 定義了模式中缺省的對(duì)象方問權(quán)限. 訪問屬性可以是 "all" 或者 "none"; 這個(gè)屬性可以被具體的權(quán)限對(duì)象繼承. 在這個(gè)例子中, 因?yàn)?access="none", 用戶只能瀏覽"Sales" 立方體, 這里明確的賦予了這個(gè)權(quán)限.
          元素 <CubeGrant> 定義了立方體的訪問權(quán)限. 就像 <SchemaGrant>, 屬性access 可以是"all" 或者 "none", 并且能夠被cube中具體的子對(duì)象繼承.
          元素 <HierarchyGrant>定義了層次的訪問權(quán)限. 屬性access 可以是"all", 意思是所有的members都是可見的; "none",意思是 hierarchy的存在對(duì)用戶是隱藏的; "custom", 你可以利用屬性 topLevel 定義可見的最高層 (阻止用戶 進(jìn)行上卷操作, 比如瀏覽稅收 上卷到 Store Country 層); 或者用屬性 bottomLevel 定義可見的最底層 (這里阻止用戶查看顧客個(gè)人的細(xì)節(jié)數(shù)據(jù));或者控制用戶查看哪一個(gè)成員集合,通過嵌套定義元素 <MemberGrant>.
          你也可以只定義元素 <MemberGrant> ,如果模式的<HierarchyGrant> 有屬性access="custom". Member grants 賦予 (或者取消) 訪問給定的成員, 以及它的所有子成員.
          posted @ 2008-03-26 22:30 edsonjava 閱讀(927) | 評(píng)論 (0)編輯 收藏
           
          改了不少JPivot/mondrian代碼,還修正了jpivot一個(gè)bug。

          對(duì)JPivot的jfreechart和drillthrough顯示做了增強(qiáng),終于可以拿出去給人用了。

          先說說性能問題: 先是找了一臺(tái)閑置的IBM X445 PC Server,4×2GHZ CPU,8G內(nèi)存,2×146G硬盤,操作系統(tǒng) windows 2000 , 開啟AWE 3G參數(shù)。然后裝Oracle 10g,數(shù)據(jù)倉庫模式,使用了4G AWE內(nèi)存共約4.5GB內(nèi)存。再建成一張1600萬用戶數(shù)據(jù)寬表,寬表一律使用bitmap索引,還有其他20個(gè)左右維表。 然后就簡單了,寫mondrian Cube,配JPivot。 最后搞下來的結(jié)果是:基本上mondrian 每次做group by 操作最長不超過30秒,一般在20秒左右。用戶基本可以接受。問了使用NCR的朋友,說NCR使用自己的數(shù)據(jù)庫,也基本是這樣的一個(gè)性能。 PS:偷偷問一聲,在這基礎(chǔ)上,性能還能改進(jìn)否?

           

          再說說方向問題: 我們現(xiàn)在使用2個(gè)OLAP,一個(gè)是jpivot + mondrian ,屬于ROLAP;另一個(gè)是BO intelligence + essbase,屬于MOLAP。目前的感覺是,由于DB性能強(qiáng)悍,導(dǎo)致ROLAP和MOLAP在性能上相差不大。同時(shí)ROLAP可以直接和報(bào)表系統(tǒng)共用同一張表。而MOLAP則需要使用工具來打CUBE做數(shù)據(jù)轉(zhuǎn)換,這樣在開發(fā)和維護(hù)工作量上,MOLAP比ROLAP大。 另外往往業(yè)務(wù)部門分析到最后,就是要看明細(xì)數(shù)據(jù)了,這個(gè)時(shí)候MOLAP的前端工具往往不能做好支持。而jpivot則無此問題。 綜上所述,我目前好像還沒看到必須用MOLAP的理由,聽說華為原來用M$ 的OLAP,后來好像支持不住了,就直接用回了BO 報(bào)表,呵呵。

           

          JPivot的問題: 操作太復(fù)雜,必須對(duì)OLAP的概念有清晰的了解,普通用戶無法使用。與mondrian 集成不夠緊密。mondrian不提供數(shù)據(jù)鉆取功能,該功能是jpivot自己做的,所以會(huì)導(dǎo)致數(shù)據(jù)類型格式丟失。鉆取詳細(xì)數(shù)據(jù)量無限制,導(dǎo)致內(nèi)存溢出。界面比較難看,操作方式非主流使用jpivot自己的mvc框架,不易其他框架集成 總體來說,jpivot目前已經(jīng)不是一個(gè)玩具了,完全可以用于企業(yè)級(jí)的操作,而且定位在高端業(yè)務(wù)分析人員。


          拿出來開源比較困難,一方面jpivot在不停升級(jí),另一方面我在修改的時(shí)候不顧一切,在jpivot中亂引用了mondrian代碼,還把mondrian部分無用代碼全刪了。這樣,我就在這個(gè)帖里把能共享部分都在這里帖出來。 首先是我優(yōu)化后的界面。 1.圖標(biāo)用了pentaho里面的圖標(biāo)。 2.jpivot里面其實(shí)支持3D餅圖,只是選項(xiàng)未開,我先將jfreechart升級(jí)成1.0.2,又對(duì)餅圖、線圖等做了美觀。 3.drillthrough是jpivot相對(duì)其他olap產(chǎn)品的殺手級(jí)功能,但是有不少細(xì)節(jié)未完善。我基本都一一補(bǔ)上。 在界面上可以看出,我添加了一個(gè)CSV導(dǎo)出功能(改了WCF庫),同時(shí)限制最大導(dǎo)出20萬行記錄(改了jpivot)。界面上顯示的“訪問次數(shù)”是measure的名字,實(shí)際上應(yīng)該顯示“訪問時(shí)間”,該問題暫時(shí)無解。另外修正了一下numberformat、dateformat不正確的一些問題。 4.excel導(dǎo)出時(shí),格式很難看,但是由于excel本身只支持256色,無法顯示web上的底色,所以我修改了只顯示藍(lán)色的border,底色一律為白。 附件中rar里面是web的CSS文件、Excel的生成文件和jpivot的圖表生成部分代碼,感興趣的朋友各取所需吧


          另外還把jpviot完全整合到我自己的系統(tǒng)中去了,呵呵。 可以在系統(tǒng)web界面上編寫Cube和MDX定義,Cube和MDX為一對(duì)多關(guān)系。Cube通過xsd來做校驗(yàn)。開發(fā)Cube和MDX的時(shí)候可以隨時(shí)做預(yù)覽。 然后再把一個(gè)MDX在界面發(fā)布成一個(gè)單獨(dú)的OLAP分析。 下一步的目標(biāo)是將數(shù)據(jù)權(quán)限與jpivot做整合,由于Cube的xml是由系統(tǒng)自動(dòng)生成的,所以mondrian的role配置也可以由系統(tǒng)根據(jù)配置自動(dòng)生成。 這部分代碼涉及我的系統(tǒng)和框架比較深,所以不帖代碼了哈,大家自己搞搞2天也就出來了


          還做了個(gè)及其變態(tài)的功能,就是把界面上所有顯示的jpivot cell,一個(gè)個(gè)的去取出鉆取數(shù)據(jù)的measure,然后生成csv文件,打成zip包給用戶下載或發(fā)到其他接口。 當(dāng)時(shí)我化了整整一個(gè)禮拜鉆研mondrian代碼,希望可以不用那么傻傻個(gè)一個(gè)個(gè)去鉆,結(jié)果失敗...

          我在用Jpivot的時(shí)候,發(fā)現(xiàn)用mondrian是影響取數(shù)性能其中的一個(gè)瓶頸........ 經(jīng)研究.....我們自己修改了jpivot和wcf的一些代碼來適應(yīng)我們自己的項(xiàng)目.........以下是我做的一些修改.....想聽聽大家的意見 1.脫離mondrian.直接寫dll的方式取數(shù),然后生成XML數(shù)據(jù) .我發(fā)現(xiàn)脫離mondrian自己寫了一個(gè)DLL去調(diào)用MSSQL 2000 的OLAP,數(shù)度很快........... 2 .修改界面的顯示方式 上面也說道.Jpivot的界面一個(gè)不好看,二是用起來很不方便.比如取維度等的時(shí)候....一層一層的進(jìn)去實(shí)在很麻煩.... a.修改取維度的方式 我們參照ms的做法 做成一個(gè)了一個(gè)樹的取數(shù),研究jpivot里面的代碼.如果直接用jpivot的代碼取數(shù)據(jù)十分慢.這樣我自己通過AJAX和Jpivot結(jié)合,動(dòng)態(tài)生成樹的結(jié)構(gòu),然后在樹上取維度的時(shí)候,直接通過鼠標(biāo)托到選擇維度textbox上.........依照條件生成相應(yīng)MDX....顯示數(shù)據(jù)..... b.修改數(shù)據(jù)顯示的樣式.和取維度,生成MDX分開了. 顯示數(shù)據(jù)我用了另外一種方式顯示.就是用Frame分為上下兩層.....上下兩層可以通過按鈕擴(kuò)大整個(gè)頁面........ 3. 集成在自己的框架中 集成在自己的框架中,我個(gè)人覺得是比較麻煩的一件事情.一點(diǎn)小事沒有搞好就很麻煩...因?yàn)槲覀兪怯肑SF開發(fā)的.所以依照J(rèn)pivot....自己寫了一些組件來輔助開發(fā),我自己開發(fā)主要改成比較像ms 2000 的olap分析方法... 還未完成的需求 JFreeChar的功能還需要加強(qiáng). 個(gè)人感覺:jpivot是很不錯(cuò).可是不能一拿來就用..我發(fā)現(xiàn)好多人用jpivot都要修改好多東西....但是修改起來又比較麻煩....java,j2ee,xml ,xslt,javascript,taglib.....好多東西都要懂.....
          posted @ 2008-03-26 22:28 edsonjava 閱讀(2244) | 評(píng)論 (1)編輯 收藏
           

          This documentation is related to the displaytag 1.1.x releases.

          The latest available release is 1.1.1

          Displaytag 1.1 offers several enhancements over 1.0: the most notable news are support for partial lists and enhanced decorator APIs, but there is also a lot more. Be sure to read the migration guide for upgrading an existing application from displaytag 1.0. A full changelog is also available.

          Overview

          The display tag library is an open source suite of custom tags that provide high-level web presentation patterns which will work in an MVC model. The library provides a significant amount of functionality while still being easy to use.

          What can I do with it?

          Actually the display tag library can just... display tables! Give it a list of objects and it will handle column display, sorting, paging, cropping, grouping, exporting, smart linking and decoration of a table in a customizable XHTML style.

          The tables in the sample images below were generated from lists using the <display:table> tag:

          sample tables produced with the display:table tag
          posted @ 2008-02-23 23:47 edsonjava 閱讀(526) | 評(píng)論 (0)編輯 收藏
           
          近在論證java領(lǐng)域的開源BI前端框架,把隨手記得東西和大家分享下.
          因?yàn)橹豢戳藥滋欤袥]時(shí)間整理所以看起來比較亂,也不是很深入。

          目前在java領(lǐng)域較常見的BI前端框架(商業(yè)智能項(xiàng)目)主要有以下幾個(gè)Pentaho,spagoBi, OpenI, JASPER intelligence等開源框架。

           他們都有自己的強(qiáng)項(xiàng)和不足,下面簡要介紹下:

          輕量級(jí)的:

          OpenI使用Mondrian和Jpivot框架,報(bào)表引擎是jasper report,數(shù)據(jù)挖掘接口是R-Project,

          相對(duì)來說開發(fā)和學(xué)習(xí)比較簡單,而且OpenI支持使用MS的數(shù)據(jù)倉庫(xmla),但是其國際化比較失敗(中文亂碼),要深入改造。

           JASPER intelligence也是個(gè)輕型項(xiàng)目,對(duì)jasper report的支持最好,所以報(bào)表部分比較好。

           重量級(jí)的:

          PentahospagoBi是兩個(gè)比較大的框架了,集成了相當(dāng)多的開源項(xiàng)目,JfreeReport、Mondrian、Kettle、Weka基本都使用了。特別適合大型復(fù)雜項(xiàng)目的開發(fā)。

                Pentaho在中國使用的比較多,文檔什么的也多一點(diǎn)。尤其值得一提的是網(wǎng)絡(luò)上對(duì)他的中文支持做的相當(dāng)好,很多志愿者翻譯了它的文檔。這給我們開發(fā)帶來很大便利。

           

                Pentaho的模塊工作流引擎、中心資源庫、審計(jì)組件、報(bào)表設(shè)計(jì)工具、ETL工具、OLAP Server、多維展示、數(shù)據(jù)挖掘組件各種組建都有。

          而且Pentaho得到了很大的投資,開發(fā)后勁很大,而且會(huì)有付費(fèi)的官方發(fā)售版本。 

          http://blog.csdn.net/dust_bug/archive/2006/09/18/1240753.aspx

          這個(gè)是Pentaho源代碼閱讀報(bào)告》,介紹Pentaho構(gòu)架相當(dāng)?shù)娜妗?O:P> 

          Pentaho的中文論壇在http://www.bipub.org/ 

          Pentaho相對(duì)spagoBi來說功能較強(qiáng),尤其是工作流一塊做的相當(dāng)不錯(cuò)。

          官方站的demos在http://www.pentaho.com/products/demos/

           spagoBi功能也很強(qiáng),尤其是最近發(fā)布的1。9版本,在http://spagobi.eng.it:8080/sbiportal/faces/public/exo(或http://spagobi.eng.it:8080/sbiportal

          的demos里展現(xiàn)了spagoBi很多功能。

           后記
          這幾款BI框架因?yàn)槎际情_源的前端框架,所以核心部分使用的還是一些開源項(xiàng)目,

          Mondrian,Jpivot,JfreeReport,所以在使用的時(shí)候搭建合適的框架會(huì)占用項(xiàng)目很大一部分時(shí)間,但是一旦框架搭建好了,基本就可以象流水線一樣出報(bào)表了。

          但是期望在原始功能上添加性能功能是比較麻煩的,為了一個(gè)新加的功能可能需要相當(dāng)長的時(shí)間來實(shí)現(xiàn)。

          另外這些開源框架的權(quán)限管理都不怎么強(qiáng),可能需要改造。

          另外,全球話的問題也是問題。象OpenI完全不支持中文,必須改造。

          posted @ 2008-02-23 23:38 edsonjava 閱讀(961) | 評(píng)論 (0)編輯 收藏
           

          我們都知道“瞎子摸象”的故事。不同的瞎子對(duì)大象的認(rèn)識(shí)不同,因?yàn)樗麄冎徽J(rèn)識(shí)了自己摸到的地方。而企業(yè)如果要避免重犯這樣的錯(cuò)誤,那就離不開商務(wù)智能(BI)。專家認(rèn)為,BI對(duì)于企業(yè)的重要性就像聰明才智對(duì)于個(gè)人的重要性。歐美企業(yè)的經(jīng)驗(yàn)也證明,企業(yè)避免無知和一知半解危險(xiǎn)的有效手段就是商務(wù)智能。商務(wù)智能旨在充分利用企業(yè)在日常經(jīng)營過程中收集的大量數(shù)據(jù)和資料,并將它們轉(zhuǎn)化為信息和知識(shí)來免除各種無知狀態(tài)和瞎猜行為。   

          支持BI的開源工具數(shù)量眾多,但是大多數(shù)的工具都是偏重某方面的。例如,CloverETL偏重ETL,JPivot偏重多維分析展現(xiàn),Mondrian是OLAP服務(wù)器。而Bee、Pentaho和SpagoBI等項(xiàng)目則針對(duì)商務(wù)智能問題提供了完整的解決方案。

          ETL 工具

          ETL開源工具主要包括CloverETL和Octupus等。

          (1)CloverETL是一個(gè)Java的ETL框架,用來轉(zhuǎn)換結(jié)構(gòu)化的數(shù)據(jù),支持多種字符集之間的轉(zhuǎn)換(如ASCII、UTF-8和ISO-8859-1等);支持JDBC,同時(shí)支持dBase和FoxPro數(shù)據(jù)文件;支持基于XML的轉(zhuǎn)換描述。

          (2)Octupus是一個(gè)基于Java的ETL工具,它也支持JDBC數(shù)據(jù)源和基于XML的轉(zhuǎn)換定義。Octupus提供通用的方法進(jìn)行數(shù)據(jù)轉(zhuǎn)換,用戶可以通過實(shí)現(xiàn)轉(zhuǎn)換接口或者使用Jscript代碼來定義轉(zhuǎn)換流程。

          OLAP服務(wù)器

          (1)Lemur主要面向HOLAP,雖然采用C++編寫,但是可以被其他語言的程序所調(diào)用。Lemur支持基本的操作,如切片、切塊和旋轉(zhuǎn)等基本操作。

          (2)Mondrian面向ROLAP包含4層:表示層、計(jì)算層、聚集層、存儲(chǔ)層。

          ● 表示層:指最終呈現(xiàn)在用戶顯示器上的以及與用戶之間的交互,有許多方法來展現(xiàn)多維數(shù)據(jù),包括數(shù)據(jù)透視表、餅、柱、線狀圖。

          ● 計(jì)算層:分析、驗(yàn)證、執(zhí)行MDX查詢。

          ● 聚集層:一個(gè)聚集指內(nèi)存中一組計(jì)算值(cell),這些值通過維列來限制。計(jì)算層發(fā)送單元請(qǐng)求,如果請(qǐng)求不在緩存中,或者不能通過旋轉(zhuǎn)聚集導(dǎo)出的話,那么聚集層向存儲(chǔ)層發(fā)送請(qǐng)求。聚合層是一個(gè)數(shù)據(jù)緩沖層,從數(shù)據(jù)庫來的單元數(shù)據(jù),聚合后提供給計(jì)算層。聚合層的主要作用是提高系統(tǒng)的性能。

          ● 存儲(chǔ)層:提供聚集單元數(shù)據(jù)和維表的成員。包括三種需要存儲(chǔ)的數(shù)據(jù),分別是事實(shí)數(shù)據(jù)、聚集和維。

          OLAP客戶端

          JPivot是JSP風(fēng)格的標(biāo)簽庫,用來支持OLAP表,使用戶可以執(zhí)行典型的OLAP操作,如切片、切塊、上鉆、下鉆等。JPivot使用Mondrian服務(wù)器,分析結(jié)果可以導(dǎo)出為Excel或PDF文件格式。

          數(shù)據(jù)庫管理系統(tǒng)

          主要的開源工具包括MonetDB、MySQL、MaxDB和PostgreSQL等。這些數(shù)據(jù)庫都被設(shè)計(jì)用來支持BI環(huán)境。MySQL、MaxDB和PostgreSQL均支持單向的數(shù)據(jù)復(fù)制。BizGres項(xiàng)目的目的在于使PostgreSQL成為數(shù)據(jù)倉庫和 BI的開源標(biāo)準(zhǔn)。BizGres為BI環(huán)境構(gòu)建專用的完整數(shù)據(jù)庫平臺(tái)。

          完整的BI開源解決方案

          1.Pentaho 公司的Pentaho BI 平臺(tái)

          它是一個(gè)以流程為中心的、面向解決方案的框架,具有商務(wù)智能組件。BI 平臺(tái)是以流程為中心的,其中樞控制器是一個(gè)工作流引擎。工作流引擎使用流程定義來定義在 BI 平臺(tái)上執(zhí)行的商務(wù)智能流程。流程可以很容易被定制,也可以添加新的流程。BI 平臺(tái)包含組件和報(bào)表,用以分析這些流程的性能。BI 平臺(tái)是面向解決方案的,平臺(tái)的操作是定義在流程定義和指定每個(gè)活動(dòng)的 action 文檔里。這些流程和操作共同定義了一個(gè)商務(wù)智能問題的解決方案。這個(gè) BI 解決方案可以很容易地集成到平臺(tái)外部的商業(yè)流程。一個(gè)解決方案的定義可以包含任意數(shù)量的流程和操作。

          BI平臺(tái)包括一個(gè) BI 框架、BI 組件、一個(gè) BI 工作臺(tái)和桌面收件箱。BI 工作臺(tái)是一套設(shè)計(jì)和管理工具,集成到Eclipse環(huán)境。這些工具允許商業(yè)分析人員或開發(fā)人員創(chuàng)建報(bào)表、儀表盤、分析模型、商業(yè)規(guī)則和 BI 流程。Pentaho BI 平臺(tái)構(gòu)建于服務(wù)器、引擎和組件的基礎(chǔ)之上,包括J2EE 服務(wù)器、安全與權(quán)限控制、portal、工作流、規(guī)則引擎、圖表、協(xié)作、內(nèi)容管理、數(shù)據(jù)集成、多維分析和系統(tǒng)建模等功能。這些組件的大部分是基于標(biāo)準(zhǔn)的,可使用其他產(chǎn)品替換之。

          2.ObjectWeb

          該項(xiàng)目近日發(fā)布了SpagoBi 1.8版本。SpagoBi 是一款基于Mondrain+JProvit的BI方案,能夠通過OpenLaszlo產(chǎn)生實(shí)時(shí)報(bào)表,為商務(wù)智能項(xiàng)目提供了一個(gè)完整開源的解決方案,它涵蓋了一個(gè)BI系統(tǒng)所有方面的功能,包括:數(shù)據(jù)挖掘、查詢、分析、報(bào)告、Dashboard儀表板等等。SpagoBI使用核心系統(tǒng)與功能模塊集成的架構(gòu),這樣在確保平臺(tái)穩(wěn)定性與協(xié)調(diào)性的基礎(chǔ)上又保證了系統(tǒng)具有很強(qiáng)的擴(kuò)展能力。用戶無需使用SpagoBI的所有模塊,而是可以只利用其中的一些模塊。

          SpagoBI使用了許多已有的開源軟件,如Spago和Spagosi等。因此,SpagoBI集成了 Spago的特征和技術(shù)特點(diǎn),使用它們管理商務(wù)智能對(duì)象,如報(bào)表、OLAP分析、儀表盤、記分卡以及數(shù)據(jù)挖掘模型等。SpagoBI支持BI系統(tǒng)的監(jiān)控管理,包括商務(wù)智能對(duì)象的控制、校驗(yàn)、認(rèn)證和分配流程。SpagoBI采用Portalet技術(shù)將所有的BI對(duì)象發(fā)布到終端用戶,因此BI對(duì)象就可以集成到為特定的企業(yè)需求而已經(jīng)選擇好的Portal系統(tǒng)中去。

          3.Bee項(xiàng)目

          該項(xiàng)目是一套支持商務(wù)智能項(xiàng)目實(shí)施的工具套件,包括ETL工具和OLAP 服務(wù)器。Bee的ETL工具使用基于Perl的BEI,通過界面描述流程,以XML形式進(jìn)行存儲(chǔ)。用戶必須對(duì)轉(zhuǎn)換過程進(jìn)行編碼。Bee的ROLAP 服務(wù)器保證多通SQL 生成和強(qiáng)有力的高速緩存管理(使用MySQL數(shù)據(jù)庫管理系統(tǒng))。ROLAP服務(wù)器通過SOAP應(yīng)用接口提供豐富的客戶應(yīng)用。Web Portal作為主要的用戶接口,通過Web瀏覽器進(jìn)行報(bào)表設(shè)計(jì)、展示和管理控制,分析結(jié)果可以以Excel、PDF、PNG、PowerPoint、 text和XML等多種形式導(dǎo)出。

          Bee項(xiàng)目的特點(diǎn)在于:

          ● 簡單快捷的數(shù)據(jù)訪問;

          ● 支持預(yù)先定義報(bào)表和實(shí)時(shí)查詢;

          ● 通過拖拽方式輕松實(shí)現(xiàn)報(bào)表定制;

          ● 完整報(bào)表的輕松控制;

          ● 以表和圖進(jìn)行高質(zhì)量的數(shù)據(jù)展示。

          posted @ 2008-02-23 23:29 edsonjava 閱讀(534) | 評(píng)論 (0)編輯 收藏
           
          java /zongfeng 
          mondrian是一個(gè)olap工具,jpviot是一個(gè)顯示它處理結(jié)果的taglib,使用這2個(gè)工具可以做復(fù)雜的統(tǒng)計(jì)匯總并顯示

          OLAP:Mondrian&JPviot


          olap:online analytical processing(聯(lián)機(jī)分析處理),實(shí)時(shí)的分析大量數(shù)據(jù),其操作通常是 只讀的.online意味著即使是大量的數(shù)據(jù),系統(tǒng)對(duì)查詢的響應(yīng)也要足夠快.

          olap使用一種技術(shù)叫做multimensional analysis(多維分析),關(guān)系數(shù)據(jù)庫將數(shù)據(jù)存成行和列的形式,多維數(shù)據(jù)表包含軸和單元.

          mondrian包含4層:表示層,計(jì)算層,聚集層,存儲(chǔ)層.

          表示層:指最終呈現(xiàn)在用戶顯示器上的,以及與用戶之間的交互,有許多方法來展現(xiàn)多維數(shù)據(jù),包括數(shù)據(jù)透視表,餅,柱,線狀圖.

          計(jì)算層:分析,驗(yàn)證,執(zhí)行MDX查詢.

          聚集層:一個(gè)聚集指內(nèi)存中一組計(jì)算值(cell),這些值通過維列來限制.計(jì)算層發(fā)送單元請(qǐng)求,如果請(qǐng)求不在緩存中,或者不能通過旋轉(zhuǎn)聚集導(dǎo)出的話,聚集層向存儲(chǔ)層發(fā)送請(qǐng)求.

          聚合層是一個(gè)數(shù)據(jù)緩沖層,從數(shù)據(jù)庫來的單元數(shù)據(jù),聚合后提供給計(jì)算層。聚合層的主要作用是提高系統(tǒng)的性能。

          存儲(chǔ)層:提供聚集單元數(shù)據(jù)和維表的成員,這些層可以不在同一機(jī)子上,但是計(jì)算和聚集層必須在同一臺(tái)機(jī)子上.

          三種需要存儲(chǔ)的數(shù)據(jù):1:事實(shí)數(shù)據(jù)2:聚集3:維

          配置文件中的特定含義:
          1:cube(立方體):是維和量的集合

          2:measure(量):一個(gè)具體的測量量

          3:dimension(維):一個(gè)屬性或者一系列屬性,通過維可以將量分類

          下面是我關(guān)于jpviot的修改:jpviot是顯示mondrian的一個(gè)taglib

          問題1:讓行和列的標(biāo)題顯示為中文,此問題非常簡單,只需要在你的schema中設(shè)置一下編碼即可,例如在FoodMart中設(shè)置如下

          <?xml version="1.0" encoding="gb2312"?>

          然后可以這樣描述Measure:

          <Measure name="庫存消耗" column="store_cost" aggregator="sum" formatString="#,###.00"/>

          所有帶name屬性的都可以替換成中文,jpviot會(huì)自動(dòng)顯示這些中文.

          問題2:關(guān)于去掉Measure標(biāo)題的問題:

          默認(rèn)生成的報(bào)表中會(huì)有這么一行
          <tr>
          <th rowspan="1" colspan="2" class="corner-heading" nowrap="nowrap">&nbsp;</th><th rowspan="1" colspan="3" class="heading-heading" nowrap="nowrap"><img height="9" width="9" border="0" src="/jpivot/jpivot/table/drill-position-other.gif">Measures</th>
          </tr>

          這一行有個(gè)默認(rèn)的標(biāo)題是Measure,如果你不想刪除這一行,而僅僅想修改這個(gè)標(biāo)題的話,可以修改
          WEB-INFclassescomtonbellerjpivotmondrianresources.properties.但是注意這個(gè)文件中內(nèi)容寫成英文沒問題,如寫成中文的話應(yīng)該寫成unicode,例如023這樣的形式.

          如果你要去掉這一行的話,修改配置文件和xsl恐怕做不到,我分析了其代碼,最終在代碼層次上做了修改:
          修改的代碼為com.tonbeller.jpivot.table.ColumnAxisBuilderImpl:

          將其構(gòu)造函數(shù)中的setHierarchyHeader的參數(shù)修改為setHierarchyHeader(NO_HEADER);這個(gè)函數(shù)支持3個(gè)參數(shù),我們修改后就不會(huì)顯示那個(gè)標(biāo)題行了.

          問題3:生成圖表后自動(dòng)生成chart表的問題:

          我測試生成圖表中的中文問題都解決了,但是每次生成chart圖時(shí)會(huì)報(bào)UTF編碼錯(cuò)誤,從錯(cuò)誤判斷應(yīng)該是某個(gè)文件的編碼錯(cuò)誤,起初根據(jù)錯(cuò)誤判斷是filter的問題,可是filter那點(diǎn)代碼中根本不涉及編碼的問題.我將很多配置文件的編碼都改了也不行.因?yàn)槟莻€(gè)英文例子沒問題,我查看了JFreechart的一個(gè)servlet(org.jfree.chart.servlet.DisplayChart),因?yàn)閖pviot就是調(diào)用這個(gè)servlet實(shí)現(xiàn)繪圖的,分析這個(gè)servlet我知道它會(huì)在一個(gè)臨時(shí)目錄生成png文件,然后交給servlet寫到瀏覽器的響應(yīng)中去,我找到那個(gè)臨時(shí)目錄(tomcattemp),發(fā)現(xiàn)里面已經(jīng)生成了正確的中文圖形.從而判斷圖形生成正確,但是寫到瀏覽器中時(shí)出了問題.最后我查看能生成英文圖表的那個(gè)例子,發(fā)覺不僅僅在html中生成圖形,而且生成map.而這個(gè)map的生成全是在程序中做的,程序生成一個(gè)xml文件,通過chart.xsl解析生成map的最終html代碼.但是在程序中生成時(shí)并沒有加入編碼設(shè)置,因此問題出在生成map這兒.

          最終修改代碼如下:

          com.tonbeller.jpivot.chart.ChartComponent:

          在render函數(shù)中修改如下:

          String desc="<?xml version="1.0" encoding="gb2312"?>";
          String xchart =desc+"n"+ "<xchart>" + writeImageMap(filename, info, false) + "</xchart>";
          這樣就為xchart設(shè)置了編碼.

          問題4:修改jfreechart中的默認(rèn)字體:

          com.tonbeller.jpivot.chart.ChartComponent中定義了幾種字體,但是這幾種字體都是英文字體,我將其修改為宋體:
          把所有的字體定義都改為"SimSun"
          注意到這兒并沒有玩,如果你僅僅修改程序,仍舊會(huì)出現(xiàn)問題,報(bào)錯(cuò)說沒有適合"SimSun"的item
          同時(shí)要修改一個(gè)配置文件:WEB-INFjpivotchartchartpropertiesform.xml
          在這個(gè)配置文件中將SimSun加入其中,形式如下:

          <listBox1 type="string" modelReference="fontName" label="Title font">
          <listItem value="SansSerif" label="SansSerif"/>
          <listItem value="Serif" label="Serif"/>
          <listItem value="SimSun" label="SimSun"/>
          <listItem value="Monospaced" label="Monospaced"/>
          </listBox1>

          以上為我最近的一點(diǎn)心得,我會(huì)完善這篇文檔,將包含mondrian中schema的書寫方法和MDX查詢語言,歡迎大家交流
          link1:微軟的MDX中文文檔

          posted @ 2008-02-23 23:20 edsonjava 閱讀(884) | 評(píng)論 (0)編輯 收藏
           

          在xml應(yīng)用中,經(jīng)常將一些URL信息作為xml數(shù)據(jù)存儲(chǔ),其中URL參數(shù)有可能包含有中文字符。
          當(dāng)使用dom對(duì)xml數(shù)據(jù)進(jìn)行解析時(shí),可以對(duì)中文字符進(jìn)行編碼。
          但如果只使用xslt來顯示xml數(shù)據(jù)時(shí)(data.xml+data.xsl),發(fā)現(xiàn)此時(shí)的URL會(huì)出現(xiàn)編碼錯(cuò)誤.
          即使指定編碼類型(encoding="gb2312"),依然會(huì)出現(xiàn)同樣的問題.
          測試發(fā)現(xiàn):是IE的緩存機(jī)制問題,IE仍會(huì)把新的頁面(所鏈接的URL)的MIME內(nèi)容類型默認(rèn)為text/xml

          解決方法:
          1.指定輸出文檔類型為xml文檔  (example:data.xsl)
           <xsl:output method="xml"  encoding="gb2312" media-type="text/xml" />
          2.在新的窗口打開,給聯(lián)接增加屬性,指明目標(biāo)窗口為其他窗口  (example:data2.xsl)
           <xsl:attribute name="target">_blank</xsl:attribute>


          examples:


          /*** data.xml ***/

          <?xml version="1.0" encoding="gb2312"?>
          <?xml-stylesheet type="text/xsl" href="data.xsl"?>
          <root>
           <search>
            <url>http://www.google.com/search?q=</url>
            <word>xml數(shù)據(jù)</word>
           </search>
           <search>
            <url>http://www1.baidu.com/baidu?word=</url>
            <word>xml數(shù)據(jù)</word>
           </search>
           <search>
            <url>http://www.google.com/search?q=</url>
            <word>極限編程(xp)</word>
           </search>
           <search>
            <url>http://www1.baidu.com/baidu?word=</url>
            <word>極限編程(xp)</word>
           </search>
          </root>


          /*** data.xsl ***/

          <?xml version="1.0" encoding="gb2312"?>
          <xsl:stylesheet version="1.0" xmlns:xsl=" <!-- 去掉下面一句,將出現(xiàn)錯(cuò)誤 -->
          <xsl:output method="xml"  encoding="gb2312" media-type="text/xml" />

          <xsl:template match="/">
           <xsl:apply-templates /> 
          </xsl:template>

          <xsl:template match="search">
           <xsl:element name="a">
            <xsl:attribute name="href"><xsl:value-of select="url" /><xsl:value-of select="word" /></xsl:attribute>
            <xsl:value-of select="word" />
           </xsl:element>
           <br />
          </xsl:template>

          </xsl:stylesheet>


          /*** data2.xsl ***/

          <?xml version="1.0" encoding="gb2312"?>
          <xsl:stylesheet version="1.0" xmlns:xsl="

          <xsl:template match="/">
           <xsl:apply-templates /> 
          </xsl:template>

          <xsl:template match="search">
           <xsl:element name="a">
            <xsl:attribute name="href"><xsl:value-of select="url" /><xsl:value-of select="word" /></xsl:attribute>
            <!-- 去掉下面一句,將出現(xiàn)錯(cuò)誤 -->
            <xsl:attribute name="target">_blank</xsl:attribute>
            <xsl:value-of select="word" />
           </xsl:element>
           <br />
          </xsl:template>

          </xsl:stylesheet>

           
          軟件包:javax.servlet.http 
                所包含的接口:HttpServletRequest;HttpServletResponse;HttpSession;HttpSessionBindingListener;HttpSessionContext。
                所包含的類:Cookie;HttpServlet;HttpSessionBindingEvent;HttpUtils。

                一、HttpServletRequest接口
                定義\
                public interface HttpServletRequest extends ServletRequest;
                用來處理一個(gè)對(duì)Servlet的HTTP格式的請(qǐng)求信息。
                方法
                1、getAuthType
                public String getAuthType();
                返回這個(gè)請(qǐng)求的身份驗(yàn)證模式。
                2、getCookies
                public Cookie[] getCookies();
                返回一個(gè)數(shù)組,該數(shù)組包含這個(gè)請(qǐng)求中當(dāng)前的所有cookie。如果這個(gè)請(qǐng)求中沒有cookie,返回一個(gè)空數(shù)組。
                3、getDateHeader
                public long getDateHeader(String name);
                返回指定的請(qǐng)求頭域的值,這個(gè)值被轉(zhuǎn)換成一個(gè)反映自1970-1-1日(GMT)以來的精確到毫秒的長整數(shù)。
                如果頭域不能轉(zhuǎn)換,拋出一個(gè)IllegalArgumentException。如果這個(gè)請(qǐng)求頭域不存在,這個(gè)方法返回-1。
                4、getHeader
                public String getHeader(String name);
                返回一個(gè)請(qǐng)求頭域的值。(譯者注:與上一個(gè)方法不同的是,該方法返回一個(gè)字符串)
                如果這個(gè)請(qǐng)求頭域不存在,這個(gè)方法返回-1。
                5、getHeaderNames
                public Enumeration getHeaderNames();
                該方法返回一個(gè)String對(duì)象的列表,該列表反映請(qǐng)求的所有頭域名。
                有的引擎可能不允許通過這種方法訪問頭域,在這種情況下,這個(gè)方法返回一個(gè)空的列表。
                6、getIntHeader
                public int getIntHeader(String name);
                返回指定的請(qǐng)求頭域的值,這個(gè)值被轉(zhuǎn)換成一個(gè)整數(shù)。
                如果頭域不能轉(zhuǎn)換,拋出一個(gè)IllegalArgumentException。如果這個(gè)請(qǐng)求頭域不存在,這個(gè)方法返回-1。
                7、getMethod
                public String getMethod();
                返回這個(gè)請(qǐng)求使用的HTTP方法(例如:GET、POST、PUT)
                8、getPathInfo
                public String getPathInfo();
                這個(gè)方法返回在這個(gè)請(qǐng)求的URL的Servlet路徑之后的請(qǐng)求URL的額外的路徑信息。如果這個(gè)請(qǐng)求URL包括一個(gè)查詢字符串,在返回值內(nèi)將不包括這個(gè)查詢字符串。這個(gè)路徑在返回之前必須經(jīng)過URL解碼。如果在這個(gè)請(qǐng)求的URL的Servlet路徑之后沒有路徑信息。這個(gè)方法返回空值。
                9、getPathTranslated
                public String getPathTranslated();
                這個(gè)方法獲得這個(gè)請(qǐng)求的URL的Servlet路徑之后的額外的路徑信息,并將它轉(zhuǎn)換成一個(gè)真實(shí)的路徑。在進(jìn)行轉(zhuǎn)換前,這個(gè)請(qǐng)求的URL必須經(jīng)過URL解碼。如果在這個(gè)URL的Servlet路徑之后沒有附加路徑信息。這個(gè)方法返回空值。
                10、getQueryString
                public String getQueryString();
                返回這個(gè)請(qǐng)求URL所包含的查詢字符串。一個(gè)查詢字串符在一個(gè)URL中由一個(gè)“?”引出。如果沒有查詢字符串,這個(gè)方法返回空值。
                11、getRemoteUser
                public String getRemoteUser
                返回作了請(qǐng)求的用戶名,這個(gè)信息用來作HTTP用戶論證。
                如果在請(qǐng)求中沒有用戶名信息,這個(gè)方法返回空值。
                12、getRequestedSessionId
                public String getRequestedSessionId();
                返回這個(gè)請(qǐng)求相應(yīng)的session id。如果由于某種原因客戶端提供的session id是無效的,這個(gè)session id將與在當(dāng)前session中的session id不同,與此同時(shí),將建立一個(gè)新的session。
                如果這個(gè)請(qǐng)求沒與一個(gè)session關(guān)聯(lián),這個(gè)方法返回空值。
                13、getRequestURI
                public String getRequestURI();
                從HTTP請(qǐng)求的第一行返回請(qǐng)求的URL中定義被請(qǐng)求的資源的部分。如果有一個(gè)查詢字符串存在,這個(gè)查詢字符串將不包括在返回值當(dāng)中。例如,一個(gè)請(qǐng)求通過/catalog/books?id=1這樣的URL路徑訪問,這個(gè)方法將返回/catalog/books。這個(gè)方法的返回值包括了Servlet路徑和路徑信息。
                如果這個(gè)URL路徑中的的一部分經(jīng)過了URL編碼,這個(gè)方法的返回值在返回之前必須經(jīng)過解碼。
                14、getServletPath
                public String getServletPath();
                這個(gè)方法返回請(qǐng)求URL反映調(diào)用Servlet的部分。例如,一個(gè)Servlet被映射到/catalog/summer這個(gè)URL路徑,而一個(gè)請(qǐng)求使用了/catalog/summer/casual這樣的路徑。所謂的反映調(diào)用Servlet的部分就是指/catalog/summer。
                如果這個(gè)Servlet不是通過路徑匹配來調(diào)用。這個(gè)方法將返回一個(gè)空值。
                15、getSession
                public HttpSession getSession();
                public HttpSession getSession(boolean create);
                返回與這個(gè)請(qǐng)求關(guān)聯(lián)的當(dāng)前的有效的session。如果調(diào)用這個(gè)方法時(shí)沒帶參數(shù),那么在沒有session與這個(gè)請(qǐng)求關(guān)聯(lián)的情況下,將會(huì)新建一個(gè)session。如果調(diào)用這個(gè)方法時(shí)帶入了一個(gè)布爾型的參數(shù),只有當(dāng)這個(gè)參數(shù)為真時(shí),session才會(huì)被建立。
                為了確保session能夠被完全維持。Servlet開發(fā)者必須在響應(yīng)被提交之前調(diào)用該方法。
                如果帶入的參數(shù)為假,而且沒有session與這個(gè)請(qǐng)求關(guān)聯(lián)。這個(gè)方法會(huì)返回空值。
                16、isRequestedSessionIdValid
                public boolean isRequestedSessionIdValid();
                這個(gè)方法檢查與此請(qǐng)求關(guān)聯(lián)的session當(dāng)前是不是有效。如果當(dāng)前請(qǐng)求中使用的session無效,它將不能通過getSession方法返回。
                17、isRequestedSessionIdFromCookie
                public boolean isRequestedSessionIdFromCookie();
                如果這個(gè)請(qǐng)求的session id是通過客戶端的一個(gè)cookie提供的,該方法返回真,否則返回假。
                18、isRequestedSessionIdFromURL
                public boolean isRequestedSessionIdFromURL();
                如果這個(gè)請(qǐng)求的session id是通過客戶端的URL的一部分提供的,該方法返回真,否則返回假。請(qǐng)注意此方法與isRequestedSessionIdFromUrl在URL的拼寫上不同。
                以下方法將被取消\

                19、isRequestedSessionIdFromUrl
                public boolean isRequestedSessionIdFromUrl();
                該方法被isRequestedSessionIdFromURL代替。

                二、HttpServletResponse接口
                定義\

                public interface HttpServletResponse extends ServletResponse
                描述一個(gè)返回到客戶端的HTTP回應(yīng)。這個(gè)接口允許Servlet程序員利用HTTP協(xié)議規(guī)定的頭信息。
                成員變量
                public static final int SC_CONTINUE = 100;
                public static final int SC_SWITCHING_PROTOCOLS = 101;
                public static final int SC_OK = 200;
                public static final int SC_CREATED = 201;
                public static final int SC_ACCEPTED = 202;
                public static final int SC_NON_AUTHORITATIVE_INFORMATION = 203;
                public static final int SC_NO_CONTENT = 204;
                public static final int SC_RESET_CONTENT = 205;
                public static final int SC_PARTIAL_CONTENT = 206;
                public static final int SC_MULTIPLE_CHOICES = 300;
                public static final int SC_MOVED_PERMANENTLY = 301;
                public static final int SC_MOVED_TEMPORARILY = 302;
                public static final int SC_SEE_OTHER = 303;
                public static final int SC_NOT_MODIFIED = 304;
                public static final int SC_USE_PROXY = 305;
                public static final int SC_BAD_REQUEST = 400;
                public static final int SC_UNAUTHORIZED = 401;
                public static final int SC_PAYMENT_REQUIRED = 402;
                public static final int SC_FORBIDDEN = 403;
                public static final int SC_NOT_FOUND = 404;
                public static final int SC_METHOD_NOT_ALLOWED = 405;
                public static final int SC_NOT_ACCEPTABLE = 406;
                public static final int SC_PROXY_AUTHENTICATION_REQUIRED = 407;
                public static final int SC_REQUEST_TIMEOUT = 408;
                public static final int SC_CONFLICT = 409;
                public static final int SC_GONE = 410;
                public static final int SC_LENGTH_REQUIRED = 411;
                public static final int SC_PRECONDITION_FAILED = 412;
                public static final int SC_REQUEST_ENTITY_TOO_LARGE = 413;
                public static final int SC_REQUEST_URI_TOO_LONG = 414;
                public static final int SC_UNSUPPORTED_MEDIA_TYPE = 415;
                public static final int SC_INTERNAL_SERVER_ERROR = 500;
                public static final int SC_NOT_IMPLEMENTED = 501;
                public static final int SC_BAD_GATEWAY = 502;
                public static final int SC_SERVICE_UNAVAILABLE = 503;
                public static final int SC_GATEWAY_TIMEOUT = 504;
                public static final int SC_HTTP_VERSION_NOT_SUPPORTED = 505;
                以上HTTP產(chǎn)狀態(tài)碼是由HTTP/1.1定義的。
                方法
                1、addCookie
                public void addCookie(Cookie cookie);
                在響應(yīng)中增加一個(gè)指定的cookie。可多次調(diào)用該方法以定義多個(gè)cookie。為了設(shè)置適當(dāng)?shù)念^域,該方法應(yīng)該在響應(yīng)被提交之前調(diào)用。
                2、containsHeader
                public boolean containsHeader(String name);
                檢查是否設(shè)置了指定的響應(yīng)頭。
                3、encodeRedirectURL
                public String encodeRedirectURL(String url);
                對(duì)sendRedirect方法使用的指定URL進(jìn)行編碼。如果不需要編碼,就直接返回這個(gè)URL。之所以提供這個(gè)附加的編碼方法,是因?yàn)樵趓edirect的情況下,決定是否對(duì)URL進(jìn)行編碼的規(guī)則和一般情況有所不同。所給的URL必須是一個(gè)絕對(duì)URL。相對(duì)URL不能被接收,會(huì)拋出一個(gè)IllegalArgumentException。
                所有提供給sendRedirect方法的URL都應(yīng)通過這個(gè)方法運(yùn)行,這樣才能確保會(huì)話跟蹤能夠在所有瀏覽器中正常運(yùn)行。
                4、encodeURL
                public String encodeURL(String url);
                對(duì)包含session ID的URL進(jìn)行編碼。如果不需要編碼,就直接返回這個(gè)URL。Servlet引擎必須提供URL編碼方法,因?yàn)樵谟行┣闆r下,我們將不得不重寫URL,例如,在響應(yīng)對(duì)應(yīng)的請(qǐng)求中包含一個(gè)有效的session,但是這個(gè)session不能被非URL的(例如cookie)的手段來維持。
                所有提供給Servlet的URL都應(yīng)通過這個(gè)方法運(yùn)行,這樣才能確保會(huì)話跟蹤能夠在所有瀏覽器中正常運(yùn)行。
                5、sendError
                public void sendError(int statusCode) throws IOException;
                public void sendError(int statusCode, String message) throws
                   IOException;
                用給定的狀態(tài)碼發(fā)給客戶端一個(gè)錯(cuò)誤響應(yīng)。如果提供了一個(gè)message參數(shù),這將作為響應(yīng)體的一部分被發(fā)出,否則,服務(wù)器會(huì)返回錯(cuò)誤代碼所對(duì)應(yīng)的標(biāo)準(zhǔn)信息。
                調(diào)用這個(gè)方法后,響應(yīng)立即被提交。在調(diào)用這個(gè)方法后,Servlet不會(huì)再有更多的輸出。
                6、sendRedirect
                public void sendRedirect(String location) throws IOException;
                使用給定的路徑,給客戶端發(fā)出一個(gè)臨時(shí)轉(zhuǎn)向的響應(yīng)(SC_MOVED_TEMPORARILY)。給定的路徑必須是絕對(duì)URL。相對(duì)URL將不能被接收,會(huì)拋出一個(gè)IllegalArgumentException。
                這個(gè)方法必須在響應(yīng)被提交之前調(diào)用。調(diào)用這個(gè)方法后,響應(yīng)立即被提交。在調(diào)用這個(gè)方法后,Servlet不會(huì)再有更多的輸出。
                7、setDateHeader
                public void setDateHeader(String name, long date);
                用一個(gè)給定的名稱和日期值設(shè)置響應(yīng)頭,這里的日期值應(yīng)該是反映自1970-1-1日(GMT)以來的精確到毫秒的長整數(shù)。如果響應(yīng)頭已經(jīng)被設(shè)置,新的值將覆蓋當(dāng)前的值。
                8、setHeader
                public void setHeader(String name, String value);
                用一個(gè)給定的名稱和域設(shè)置響應(yīng)頭。如果響應(yīng)頭已經(jīng)被設(shè)置,新的值將覆蓋當(dāng)前的值。
                9、setIntHeader
                public void setIntHeader(String name, int value);
                用一個(gè)給定的名稱和整形值設(shè)置響應(yīng)頭。如果響應(yīng)頭已經(jīng)被設(shè)置,新的值將覆蓋當(dāng)前的值。
                10、setStatus
                public void setStatus(int statusCode);
                這個(gè)方法設(shè)置了響應(yīng)的狀態(tài)碼,如果狀態(tài)碼已經(jīng)被設(shè)置,新的值將覆蓋當(dāng)前的值。
                以下的幾個(gè)方法將被取消\
                11、encodeRedirectUrl
                public String encodeRedirectUrl(String url);
                該方法被encodeRedirectURL取代。 
                12、encodeUrl
                public String encodeUrl(String url);
                該方法被encodeURL取代。 
                13、setStatus
                public void setStatus(int statusCode, String message);
                這個(gè)方法設(shè)置了響應(yīng)的狀態(tài)碼,如果狀態(tài)碼已經(jīng)被設(shè)置,新的值將覆蓋當(dāng)前的值。如果提供了一個(gè)message,它也將會(huì)被作為響應(yīng)體的一部分被發(fā)送。

                三、HttpSession接口
                定義\
                public interface HttpSession
                這個(gè)接口被Servlet引擎用來實(shí)現(xiàn)在HTTP客戶端和HTTP會(huì)話兩者的關(guān)聯(lián)。這種關(guān)聯(lián)可能在多外連接和請(qǐng)求中持續(xù)一段給定的時(shí)間。session用來在無狀態(tài)的HTTP協(xié)議下越過多個(gè)請(qǐng)求頁面來維持狀態(tài)和識(shí)別用戶。
                一個(gè)session可以通過cookie或重寫URL來維持。
                方法
                1、getCreationTime
                public long getCreationTime();
                返回建立session的時(shí)間,這個(gè)時(shí)間表示為自1970-1-1日(GMT)以來的毫秒數(shù)。 
                2、getId
                public String getId();
                返回分配給這個(gè)session的標(biāo)識(shí)符。一個(gè)HTTP session的標(biāo)識(shí)符是一個(gè)由服務(wù)器來建立和維持的唯一的字符串。
                3、getLastAccessedTime
                public long getLastAccessedTime();
                返回客戶端最后一次發(fā)出與這個(gè)session有關(guān)的請(qǐng)求的時(shí)間,如果這個(gè)session是新建立的,返回-1。這個(gè)時(shí)間表示為自1970-1-1日(GMT)以來的毫秒數(shù)。 
                4、getMaxInactiveInterval
                public int getMaxInactiveInterval();
                返加一個(gè)秒數(shù),這個(gè)秒數(shù)表示客戶端在不發(fā)出請(qǐng)求時(shí),session被Servlet引擎維持的最長時(shí)間。在這個(gè)時(shí)間之后,Servlet引擎可能被Servlet引擎終止。如果這個(gè)session不會(huì)被終止,這個(gè)方法返回-1。
                當(dāng)session無效后再調(diào)用這個(gè)方法會(huì)拋出一個(gè)IllegalStateException。
                5、getValue
                public Object getValue(String name);
                返回一個(gè)以給定的名字綁定到session上的對(duì)象。如果不存在這樣的綁定,返回空值。
                當(dāng)session無效后再調(diào)用這個(gè)方法會(huì)拋出一個(gè)IllegalStateException。
                6、getValueNames
                public String[] getValueNames();
                以一個(gè)數(shù)組返回綁定到session上的所有數(shù)據(jù)的名稱。
                當(dāng)session無效后再調(diào)用這個(gè)方法會(huì)拋出一個(gè)IllegalStateException。
                7、invalidate
                public void invalidate();
                這個(gè)方法會(huì)終止這個(gè)session。所有綁定在這個(gè)session上的數(shù)據(jù)都會(huì)被清除。并通過HttpSessionBindingListener接口的valueUnbound方法發(fā)出通告。
                8、isNew
                public boolean isNew();
                返回一個(gè)布爾值以判斷這個(gè)session是不是新的。如果一個(gè)session已經(jīng)被服務(wù)器建立但是還沒有收到相應(yīng)的客戶端的請(qǐng)求,這個(gè)session將被認(rèn)為是新的。這意味著,這個(gè)客戶端還沒有加入會(huì)話或沒有被會(huì)話公認(rèn)。在他發(fā)出下一個(gè)請(qǐng)求時(shí)還不能返回適當(dāng)?shù)膕ession認(rèn)證信息。
                當(dāng)session無效后再調(diào)用這個(gè)方法會(huì)拋出一個(gè)IllegalStateException。
                9、putValue
                public void putValue(String name, Object value);
                以給定的名字,綁定給定的對(duì)象到session中。已存在的同名的綁定會(huì)被重置。這時(shí)會(huì)調(diào)用HttpSessionBindingListener接口的valueBound方法。
                當(dāng)session無效后再調(diào)用這個(gè)方法會(huì)拋出一個(gè)IllegalStateException。
                10、removeValue
                public void removeValue(String name);
                取消給定名字的對(duì)象在session上的綁定。如果未找到給定名字的綁定的對(duì)象,這個(gè)方法什么出不做。 這時(shí)會(huì)調(diào)用HttpSessionBindingListener接口的valueUnbound方法。
                當(dāng)session無效后再調(diào)用這個(gè)方法會(huì)拋出一個(gè)IllegalStateException。
                11、setMaxInactiveInterval
                public int setMaxInactiveInterval(int interval);
                設(shè)置一個(gè)秒數(shù),這個(gè)秒數(shù)表示客戶端在不發(fā)出請(qǐng)求時(shí),session被Servlet引擎維持的最長時(shí)間。
                以下這個(gè)方法將被取消\
                12、getSessionContext
                public HttpSessionContext getSessionContext();
                返回session在其中得以保持的環(huán)境變量。這個(gè)方法和其他所有HttpSessionContext的方法一樣被取消了。

                四、HttpSessionBindingListener接口
                定義\
                public interface HttpSessionBindingListener
                這個(gè)對(duì)象被加入到HTTP的session中,執(zhí)行這個(gè)接口會(huì)通告有沒有什么對(duì)象被綁定到這個(gè)HTTP session中或被從這個(gè)HTTP session中取消綁定。
                方法
                1、valueBound
                public void valueBound(HttpSessionBindingEvent event);
                當(dāng)一個(gè)對(duì)象被綁定到session中,調(diào)用此方法。HttpSession.putValue方法被調(diào)用時(shí),Servlet引擎應(yīng)該調(diào)用此方法。
                2、valueUnbound
                public void valueUnbound(HttpSessionBindingEvent event);
                當(dāng)一個(gè)對(duì)象被從session中取消綁定,調(diào)用此方法。HttpSession.removeValue方法被調(diào)用時(shí),Servlet引擎應(yīng)該調(diào)用此方法。

                五、HttpSessionContext接口
                定義\
                此接口將被取消\
                public interface HttpSessionContext
                這個(gè)對(duì)象是與一組HTTP session關(guān)聯(lián)的單一的實(shí)體。
                這個(gè)接口由于安全的原因被取消,它出現(xiàn)在目前的版本中僅僅是為了兼容性的原因。這個(gè)接口的方法將模擬以前的版本的定義返回相應(yīng)的值。
                方法
                1、getSession
                public HttpSession getSession(String sessionId);
                當(dāng)初用來返回與這個(gè)session id相關(guān)的session。現(xiàn)在返回空值。
                2、getIds
                public Enumeration getIds();
                當(dāng)初用來返回這個(gè)環(huán)境下所有session id的列表。現(xiàn)在返回空的列表。

                六、Cookie類\
                定義\
                public class Cookie implements Cloneable
                這個(gè)類描述了一個(gè)cookie,有關(guān)cookie的定義你可以參照Netscape Communications Corporation的說明,也可以參照RFC 2109。
                構(gòu)造函數(shù)
                public Cookie(String name, String value);
                用一個(gè)name-value對(duì)定義一個(gè)cookie。這個(gè)name必須能被HTTP/1.1所接受。
                以字符$開頭的name被RFC 2109保留。
                給定的name如果不能被HTTP/1.1所接受,該方法拋出一個(gè)IllegalArgumentException。
                方法
                1、getComment
                public String getComment();
                返回描述這個(gè)cookie目的的說明,如果未定義這個(gè)說明,返回空值。
                2、getDomain
                public String getDomain();
                返回這個(gè)cookie可以出現(xiàn)的區(qū)域,如果未定義區(qū)域,返回空值。
                3、getMaxAge
                public int getMaxAge();
                這個(gè)方法返回這個(gè)cookie指定的最長存活時(shí)期。如果未定義這個(gè)最長存活時(shí)期,該方法返回-1。
                4、getName
                public String getName();
                該方法返回cookie名。
                5、getPath
                public String getPath();
                返回這個(gè)cookie有效的所有URL路徑的前綴,如果未定義,返回空值。
                6、getSecure
                public boolean getSecure();
                如果這個(gè)cookie只通過安全通道傳輸返回真,否則返回假。
                7、getValue
                public String getValue();
                該方法返回cookie的值。
                8、getVersion
                public int getVersion();
                返回cookie的版本。版本1由RFC 2109解釋。版本0由Netscape Communications Corporation的說明解釋。新構(gòu)造的cookie默認(rèn)使用版本0。
                9、setComment
                public void setComment(String purpose);
                如果一個(gè)用戶將這個(gè)cookie提交給另一個(gè)用戶,必須通過這個(gè)說明描述這個(gè)cookie的目的。版本0不支持這個(gè)屬性。
                10、setDomain
                public void setDomain(String pattern);
                這個(gè)方法設(shè)置cookie的有效域的屬性。這個(gè)屬性指定了cookie可以出現(xiàn)的區(qū)域。一個(gè)有效域以一個(gè)點(diǎn)開頭(.foo.com),這意味著在指定的域名解析系統(tǒng)的區(qū)域中(可能是www.foo.com但不是a.b.foo.com)的主機(jī)可以看到這個(gè)cookie。默認(rèn)情況是,cookie只能返回保存它的主機(jī)。
                11、setMaxAge
                public void setMaxAge(int expiry);
                這個(gè)方法設(shè)定這個(gè)cookie的最長存活時(shí)期。在該存活時(shí)期之后,cookie會(huì)被終目。負(fù)數(shù)表示這個(gè)cookie不會(huì)生效,0將從客戶端刪除這個(gè)cookie。
                   12、setPath
                public void setPath(String uri);
                這個(gè)方法設(shè)置cookie的路徑屬性。客戶端只能向以這個(gè)給定的路徑String開頭的路徑返回cookie。
                13、setSecure
                public void setSecure(boolean flag);
                指出這個(gè)cookie只能通過安全通道(例如HTTPS)發(fā)送。只有當(dāng)產(chǎn)生這個(gè)cookie的服務(wù)器使用安全協(xié)議發(fā)送這個(gè)cookie值時(shí)才能這樣設(shè)置。
                14、setValue
                public void setValue(String newValue);
                設(shè)置這個(gè)cookie的值,對(duì)于二進(jìn)制數(shù)據(jù)采用BASE64編碼。
                版本0不能使用空格、{}、()、=、,、“”、/、?、@、:以及;。
                15、setVersion
                public void setVersion(int v);
                設(shè)置cookie的版本號(hào)

                七、HttpServlet類\
                定義\
                public class HttpServlet extends GenericServlet implements 
                   Serializable
                這是一個(gè)抽象類,用來簡化HTTP Servlet寫作的過程。它是GenericServlet類的擴(kuò)充,提供了一個(gè)處理HTTP協(xié)議的框架。
                在這個(gè)類中的service方法支持例如GET、POST這樣的標(biāo)準(zhǔn)的HTTP方法。這一支持過程是通過分配他們到適當(dāng)?shù)姆椒ǎɡ鏳oGet、doPost)來實(shí)現(xiàn)的。
                方法
                1、doDelete
                protected void doDelete(HttpServletRequest request,
                      HttpServletResponse response) throws ServletException,
                      IOException;
                被這個(gè)類的service方法調(diào)用,用來處理一個(gè)HTTP DELETE操作。這個(gè)操作允許客戶端請(qǐng)求從服務(wù)器上刪除URL。這一操作可能有負(fù)面影響,對(duì)此用戶就負(fù)起責(zé)任。
                這一方法的默認(rèn)執(zhí)行結(jié)果是返回一個(gè)HTTP BAD_REQUEST錯(cuò)誤。當(dāng)你要處理DELETE請(qǐng)求時(shí),你必須重載這一方法。
                2、doGet
                protected void doGet(HttpServletRequest request, 
                      HttpServletResponse response) throws ServletException,
                      IOException;
                被這個(gè)類的service方法調(diào)用,用來處理一個(gè)HTTP GET操作。這個(gè)操作允許客戶端簡單地從一個(gè)HTTP服務(wù)器“獲得”資源。對(duì)這個(gè)方法的重載將自動(dòng)地支持HEAD方法。
                GET操作應(yīng)該是安全而且沒有負(fù)面影響的。這個(gè)操作也應(yīng)該可以安全地重復(fù)。
                這一方法的默認(rèn)執(zhí)行結(jié)果是返回一個(gè)HTTP BAD_REQUEST錯(cuò)誤。
                3、doHead
                protected void doHead(HttpServletRequest request,
                      HttpServletResponse response) throws ServletException,
                      IOException;
                被這個(gè)類的service方法調(diào)用,用來處理一個(gè)HTTP HEAD操作。默認(rèn)的情況是,這個(gè)操作會(huì)按照一個(gè)無條件的GET方法來執(zhí)行,該操作不向客戶端返回任何數(shù)據(jù),而僅僅是返回包含內(nèi)容長度的頭信息。
                與GET操作一樣,這個(gè)操作應(yīng)該是安全而且沒有負(fù)面影響的。這個(gè)操作也應(yīng)該可以安全地重復(fù)。
                這個(gè)方法的默認(rèn)執(zhí)行結(jié)果是自動(dòng)處理HTTP HEAD操作,這個(gè)方法不需要被一個(gè)子類執(zhí)行。 
                4、doOptions
                protected void doOptions(HttpServletRequest request,
                      HttpServletResponse response) throws ServletException,
                      IOException;
                被這個(gè)類的service方法調(diào)用,用來處理一個(gè)HTTP OPTION操作。這個(gè)操作自動(dòng)地決定支持哪一種HTTP方法。例如,一個(gè)Servlet寫了一個(gè)HttpServlet的子類并重載了doGet方法,doOption會(huì)返回下面的頭:
                Allow: GET,HEAD,TRACE,OPTIONS
                你一般不需要重載這個(gè)方法。
                5、doPost
                protected void doPost(HttpServletRequest request,
                      HttpServletResponse response) throws ServletException,
                      IOException;
                被這個(gè)類的service方法調(diào)用,用來處理一個(gè)HTTP POST操作。這個(gè)操作包含請(qǐng)求體的數(shù)據(jù),Servlet應(yīng)該按照他行事。
                這個(gè)操作可能有負(fù)面影響。例如更新存儲(chǔ)的數(shù)據(jù)或在線購物。
                這一方法的默認(rèn)執(zhí)行結(jié)果是返回一個(gè)HTTP BAD_REQUEST錯(cuò)誤。當(dāng)你要處理POST操作時(shí),你必須在HttpServlet的子類中重載這一方法。
                6、doPut
                protected void doPut(HttpServletRequest request, 
                      HttpServletResponse response) throws ServletException,
                      IOException;
                被這個(gè)類的service方法調(diào)用,用來處理一個(gè)HTTP PUT操作。這個(gè)操作類似于通過FTP發(fā)送文件。
                這個(gè)操作可能有負(fù)面影響。例如更新存儲(chǔ)的數(shù)據(jù)或在線購物。
                這一方法的默認(rèn)執(zhí)行結(jié)果是返回一個(gè)HTTP BAD_REQUEST錯(cuò)誤。當(dāng)你要處理PUT操作時(shí),你必須在HttpServlet的子類中重載這一方法。
                7、doTrace
                protected void doTrace(HttpServletRequest request,
                      HttpServletResponse response) throws ServletException,
                      IOException;
                被這個(gè)類的service方法調(diào)用,用來處理一個(gè)HTTP TRACE操作。這個(gè)操作的默認(rèn)執(zhí)行結(jié)果是產(chǎn)生一個(gè)響應(yīng),這個(gè)響應(yīng)包含一個(gè)反映trace請(qǐng)求中發(fā)送的所有頭域的信息。
                當(dāng)你開發(fā)Servlet時(shí),在多數(shù)情況下你需要重載這個(gè)方法。
                8、getLastModified
                protected long getLastModified(HttpServletRequest request);
                返回這個(gè)請(qǐng)求實(shí)體的最后修改時(shí)間。為了支持GET操作,你必須重載這一方法,以精確地反映最后修改的時(shí)間。這將有助于瀏覽器和代理服務(wù)器減少裝載服務(wù)器和網(wǎng)絡(luò)資源,從而更加有效地工作。返回的數(shù)值是自1970-1-1日(GMT)以來的毫秒數(shù)。 
          默認(rèn)的執(zhí)行結(jié)果是返回一個(gè)負(fù)數(shù),這標(biāo)志著最后修改時(shí)間未知,它也不能被一個(gè)有條件的GET操作使用。
                9、service
                protected void service(HttpServletRequest request,
                      HttpServletResponse response) throws ServletException,
                      IOException;
                public void service(ServletRequest request, ServletResponse response)
                      throws ServletException, IOException;
                這是一個(gè)Servlet的HTTP-specific方案,它分配請(qǐng)求到這個(gè)類的支持這個(gè)請(qǐng)求的其他方法。
                當(dāng)你開發(fā)Servlet時(shí),在多數(shù)情況下你不必重載這個(gè)方法。

                八、HttpSessionBindingEvent類\
                定義\
                public class HttpSessionBindingEvent extends EventObject
                這個(gè)事件是在監(jiān)聽到HttpSession發(fā)生綁定和取消綁定的情況時(shí)連通HttpSessionBindingListener的。這可能是一個(gè)session被終止或被認(rèn)定無效的結(jié)果。
                事件源是HttpSession.putValue或HttpSession.removeValue。
                構(gòu)造函數(shù)
                public HttpSessionBindingEvent(HttpSession session, String name);
                通過引起這個(gè)事件的Session和發(fā)生綁定或取消綁定的對(duì)象名構(gòu)造一個(gè)新的HttpSessionBindingEvent。
                方法
                1、getName
                public String getName();
                返回發(fā)生綁定和取消綁定的對(duì)象的名字。
                2、getSession
                public HttpSession getSession();
                返回發(fā)生綁定和取消綁定的session的名字。

                   九、HttpUtils類\
                定義\
                public class HttpUtils
                收集HTTP Servlet使用的靜態(tài)的有效的方法。
                方法
                1、getRequestURL
                public static StringBuffer getRequestURL(HttpServletRequest
                      request);
                在服務(wù)器上重建客戶端用來建立請(qǐng)求的URL。這個(gè)方法反映了不同的協(xié)議(例如http和https)和端口,但不包含查詢字符串。
                這個(gè)方法返回一個(gè)StringBuffer而不是一個(gè)String,這樣URL可以被Servlet開發(fā)者有效地修改。
                2、parsePostData
                public static Hashtable parsePostData(int len, 
                      ServletInputstream in);
                解析一個(gè)包含MIME類型application/x-www-form-urlencoded的數(shù)據(jù)的流,并創(chuàng)建一個(gè)具有關(guān)鍵值-數(shù)據(jù)對(duì)的hash table。這里的關(guān)鍵值是字符串,數(shù)據(jù)是該字符串所對(duì)應(yīng)的值的列表。一個(gè)關(guān)鍵值可以在POST的數(shù)據(jù)中出現(xiàn)一次或多次。這個(gè)關(guān)鍵值每出現(xiàn)一次,它的相應(yīng)的值就被加入到hash table中的字符串所對(duì)應(yīng)的值的列表中。
                從POST數(shù)據(jù)讀出的數(shù)據(jù)將經(jīng)過URL解碼,+將被轉(zhuǎn)換為空格以十六進(jìn)制傳送的數(shù)據(jù)(例如%xx)將被轉(zhuǎn)換成字符。
                當(dāng)POST數(shù)據(jù)無效時(shí),該方法拋出一個(gè)IllegalArgumentException。
                3、parseQueryString
                public static Hashtable parseQueryString(String s);
                解析一個(gè)查詢字符串,并創(chuàng)建一個(gè)具有關(guān)鍵值-數(shù)據(jù)對(duì)的hash table。這里的數(shù)據(jù)是該字符串所對(duì)應(yīng)的值的列表。一個(gè)關(guān)鍵值可以出現(xiàn)一次或多次。這個(gè)關(guān)鍵值每出現(xiàn)一次,它的相應(yīng)的值就被加入到hash table中的字符串所對(duì)應(yīng)的值的列表中。
                從查詢字符串讀出的數(shù)據(jù)將經(jīng)過URL解碼,+將被轉(zhuǎn)換為空格以十六進(jìn)制傳送的數(shù)據(jù)(例如%xx)將被轉(zhuǎn)換成字符。
                當(dāng)查詢字符串無效時(shí),該方法拋出一個(gè)IllegalArgumentException。
          posted @ 2007-07-26 16:34 edsonjava 閱讀(458) | 評(píng)論 (0)編輯 收藏
           

          CVSNT 2.5.03 Installation on Windows 2003

          Author: Bo Berglund
          Notice:
          This guide is written as an installation help for CVSNT 2.5.03 and higher on Windows 2003 server.
          Most of the discussion is also valid for installation on Windows XP-Pro (see below for an important setting).
          NOTE! You cannot use XP-Home for CVSNT!
          The guide uses the Innosetup based installer that I maintain but similar results can probably be obtained by using the Innosetup installer published by Oliver Giesen as well.
          I am not using the MSI installer from the official CVSNT website since I cannot accept non-opensource software if anything else is available.

          Table of contents
          CVSNT Installation
          Configuring the server
          Adding CVS users
          Adding CVS administrators
          Disabling pserver as security measure
          The cvs passwd command for adding users
          Managing pserver and sserver users
          Using the SSPI protocol
          Fine-tuning user access of CVS
          Using spaces with CVSNT

          Links:
          CVSNT Auditing Configuration Tutorial
          Innosetup CVSNT Installer download
          CVSMailer homepage, Automatic email on commits and other events
          ViewCvs Installer download
          CVSNT command reference
          CVSNT download (where you can download the latest CVSNT versions)

          Karl Fogel's book 'Open Source development with CVS'
          The free part of Karl Fogel's book in HTML format
          DevGuy's CVS information pages
          CVS-Gui (WinCvs) homepage
          WinCvs Dialy use guide
          WinCvs 1.3 manual (PDF format)

          WinCvs download (on SourceForge)

          Installation of the CVSNT server

          File system type
          Make sure your system is only using the NTFS file system!
          Also make sure you are logged on as an administrator of the PC (using an account with administrative priviliges).
          And most important: Use the local disk on the CVSNT server!

          IMPORTANT for XP-Pro users:
          You MUST switch off Simple File Sharing, which is the default for XP (as recommended by Microsoft to make XP somewhat compatible with Win95-98-ME)!
          You do this by opening a Windows Explorer and then use the menu command Tools/Folder Options. Select the View tab and scroll down to the bottom where you find this item. Uncheck it now!
          Simple File Sharing

          Now for the actual installation and configuration:

          1. Get the latest release of CVSNT
          Get the latest CVSNT Innosetup installation from Innosetup CVSNT Installer download

          2. Create CVS directories
          Create two directories on the target machine, c:\cvsrepos and c:\cvsrepos\cvstemp. If you have a separate disk partition to spare for CVS then use that instead. The important point here is that the disk where the repository is located on is NTFS.

          3. Directory security and permissions
          Give c:\cvsrepos\cvstemp security settings that allows full control for all accounts including SYSTEM.
          Important:
          The cvstemp directory must NOT be located in either c:\WINNT\Temp or anywhere in the "C:\Documents and Settings" tree because these locations have imposed restrictions on user access!
          Notice that on XP-Pro out of the box from Microsoft the permissions cannot be set like this until "Simple File Sharing" is switched off (see above). So you must do this if you use XP-Pro. XP-Home is totally unsuitable for CVSNT!

          4. Install CVSNT
          Run the downloaded CVSNT setup file and make sure to change the installation path to c:\programs\cvsnt (I am paranoid about removing any spaces in paths used by cvs!)
          Start screen:
          Install screen #1

          License agreement:
          Install screen #2

          Install directory selection:
          Note:
          I strongly recommend that you install CVSNT to a path that does NOT contain any embedded spaces, for example like this:
          Install screen #3

          Installation component selection screen:
          Install screen #4

          Start menu selection:
          Install screen #5

          Task selection screen:
          Install screen #6

          Ready to install!
          Install screen #7

          Install in progress
          Install screen #8

          Release notes
          Install screen #9

          Installation done!
          Install screen #10


          Configuring the CVSNT server and repository


          1. CVSNT Control Panel configuration
          CVSNT is configured from the CVSNT Control Panel, which can be reached via the shortcut link placed under the Start menu during installation.
          Control Panel

          Now open the CVSNT control panel applet and do the following:

          2. Shut down the CVSNT service
          Check that the CVSNT Service is not running (Start button is enabled). This is the initial screen showing that both services are running:

          Configuration screen #1
          If it is started then stop it. You can leave the Lock Service running.

          3. Repository creation
          The tab will initially look like this:

          Configuration screen #2

          4. Add repository
          Now you will add a repository to the server. This is done using the "Add" button. When you click this a dialogue shows up where you will define your repository.

          Empty repo

          5. Repository folder
          Click the ellipsis button for Location to bring up the folder browser.
          Now you can browse to the location you want for your repository and add a new folder here.
          NOTE:
          I strongly advice NOT to use paths with embedded spaces for CVS!

          Browse for folder

          6. Name repository
          Now fill in the description and the name of the repository as well.
          NOTE:
          Do NOT
          accept the suggested name, which is the same as the folder path!
          Instead only use the bare folder name with a leading / like this:

          CVSNT AddRepository

          7. Initializing the repository
          When you click the OK button there will be a dialog where CVSNT offers to initialize the new repository.
          When you click Yes then the new folder will be converted to a real repository:

          CVSNT AddRepository


          8. First repository added!
          Now the list of repositories has been populated with the first repository:

          CVSNT Repository


          You can add as many as you like (almost) but please do not fall for the temptation to use one repository for each and every project! There are a lot of possibilities to streamline the development process using CVSNT, but many of these use the virtual modules concept and this is only possible within a single repository.


          9. Server Settings
          Now go on to the Server Settings tab.
          Here the default settings are all right for now, except the Temporary Directory setting.

          Serversettings

          NOTICE about Domains:
          You can set the Default domain entry to either the CVSNT server PC name (as in the example above) or the domain name to which the CVSNT server belongs. CVSNT will strip the domain part from all accounts that log on using the default domain before processing. All other logons will be processed using their complete names (DOMAIN\username). The result of this is that all users that "belong" to the domain specified in this box will be logged using only the account name, likewise these usernames will be supplied to the administrative scripts without the domain name. All others will have a domain name added. This must be accounted for in any admin script used.
          The CVSROOT/users file is one such admin file that needs to be handled with care concerning domain and non-domain entries.

          Temp dir: Use the ellipsis button to browse for the folder prepared for this purpose above:

          Tempdir

          10. Compatibility
          On the next tab (Compatibility Options) there is nothing you need to change for now:

          Serversettings screen #1


          11. Plugins and protocols
          The Plugins tab define a lot of the extra features of CVSNT including some aspects of the connection protocols. The sceen list the available plugins and when you select a line you will be able to configure this plugin by clicking the configure button:

          Serversettings screen #1


          12. Sserver configuration
          Here is the configuration window for the SSERVER protocol plugin. Please set it like this:

          SSPI config screen


          13. Advanced settings
          The final tab on the Control Panel deals with advanced configuration settings and you need not change anything here.

          Configuration screen #1


          14. Apply configuration changes
          Now click the Apply button! This is really important, nothing will happen unless you do this! Note that after you have done this the Apply button is disabled.

          15. Start the CVSNT service
          Go back to the first tab and click the Start button. After a few moments the Stop button will be highlighted.
          Now CVSNT runs (success!)

          16. Restart the server
          In order for you to be able to use the command line cvs you need to have the path variable set to include the location of the cvs.exe just installed (c:\programs\cvsnt). Since the installer will have put this into the system path variable it will work if you restart the server.
          You can check this by going to a command window and typing the command:
          cvs --ver
          If this results in an eror message then you should restart the server PC before continuing.

          Adding and managing CVS users for pserver and sserver access

          This is a step that is only needed if you plan on using the sserver or pserver protocols with this CVS server. If your users are all on Windows PC:s pserver is not recommended since it has inherent security flaws. Instead use SSPI because that protocols integrate much better with Windows. If you decide to go with sspi (recommended) then you can skip the discussion on how to add and manage users in this section.

          1. Creating CVS accounts on the server
          In order for pserver and sserver to work you have to define CVS users, but before you can do this you need to create two real accounts on the server. These accounts will be used by the CVS users as the working accounts.
          You need one account which will be a CVS administrative account and one which will be a normal user account. Note that the CVS administrator need not be a server administrator!

          Usermanager

          The two accounts are added through the Users dialog in Computer Management.
          I have used the account names cvsadmin and cvsuser as shown above.

          2. Adding CVS users
          Open a command window and do the following (replace items <text> with the real values from your system).

          set cvsroot=:sspi:<computername>:/TEST
          cvs passwd -a <account name>

          You will now be asked to enter a password for this user. This password is only for CVS use so it should not be the real system password! Enter the password twice.
          Now the CVSROOT/passwd file will be created and the user you entered will be added to the list in this file.
          This step is necessary if you are going to use the pserver or sserver protocol in the future since there is no way to log in with pserver/sserver unless there is a passwd file present with the user listed.

          Important note:
          Any user entered like this MUST be an NT user on the local system! CVS will not accept any user login that is not connected to a "real" account.

          3. Aliasing CVS users to real accounts
          In order to have many CVS user logins you don't need to create masses of system accounts! Instead you can "alias" a CVS login to a "real" account using this command:

          cvs passwd -r <real accountname> -a <cvs login name>

          What will happen now is that to CVS the user will be known and registered as the CVS login given in the command, but for file operations that will encounter permission issues the commands will be executed in the context of the real system account that was aliased. This makes it possible to use NTFS file system permissions to limit access to certain parts of the repository to some users. You simply create a system account for which you set limited permissions and then you alias the CVS login to this user.

          Note that this command will fail if there is a space embedded in the real account name! DON'T ever use spaces in these contexts!!!!! (But using quotes may solve the problem like this:
          cvs passwd -r "system admin" -a "new user"
          Since I don't have a valid user with embedded space I could not check the quotes trick with the valid user name parameter, but adding a CVS login with space embedded *can* be done with quotes.)

          Examples:
          cvs passwd -r cvsuser -a charlie

          or if you want the new user to be a CVS administrator:

          cvs passwd -r cvsadmin -a rogerh

          Note about Domain users:
          You can add domain users with the following command:
          cvs passwd -r <real accountname> -D <domain name> -a <cvs login name>
          This command is reported by a user to have worked for him. I cannot check it because I don't have a domain. But based on information from the mail list I think that it will only work if there is a trust between the CVSNT server PC and the domain controller. If the CVSNT server PC is a member of the domain then this is the case.

          The server is now ready to be used and you can check the pserver functionality by doing this:

          4. Testing the CVS connection with sserver
          Open another command window and type:
          set cvsroot=:sserver:<user>@<computername>:/TEST
          Replace <user> and <computername> with valid entries like:
          set cvsroot=:sserver:charlie@cvsserver:/TEST

          Then:
          cvs login (enter password on prompt)
          cvs ls -l -R
          (this should give you a list of the files in TEST/CVSROOT)

          5. Testing the CVS connection with pserver
          Open another command window and type:
          set cvsroot=:pserver:<user>@<computername>:/TEST
          Replace <user> and <computername> with valid entries like:
          set cvsroot=:pserver:charlie@cvsserver:/TEST

          Then:
          cvs login (enter password on prompt)
          cvs ls -l -R
          (this should give you a list of the files in TEST/CVSROOT)

          6. Testing the CVS connection from another PC
          Open a command window on another PC where you have installed the CVSNT in client only mode and type:
          set cvsroot=:sserver:<user>@<computername>:/TEST
          Replace <user> and <computername> with valid entries like:
          set cvsroot=:pserver:charlie@cvsserver:/TEST

          Then:
          cvs login (enter password on prompt)
          cvs ls -l -R
          (this should give you a list of the files in TEST/CVSROOT)

          If you cannot get this far, for example if the login fails, then you should check the Windows Firewall settings on the CVSNT server:

          7. Modifying Windows Firewall to allow CVS calls

          • Go to Control Panel
          • Open the Windows Firewall item.
          • Select the Exceptions tab
          • Click the "Add port" button
          • Enter the name CVSNT and port number 2401 as a TCP port
          • Accept back to the main screen
          • Make sure Windows Firewall is set to ON

          Configuration screen #1

          Configuration screen #1

          Configuration screen #1

           

          Administrating the repository, users with admin rights

          There have been a number of reports that people have not been able to add users or execute the cvs admin command even though they were members of the Administartors group or even of Domain Admins. In order to avoid this there is a simple way to manage who will have admin rights on the CVSNT server. It is done through the CVSROOT/admin file.
          Here is how to:

          • Create a text file called admin (no extension) inside the CVSROOT directory of the repository.
          • Edit this file by adding on separate lines the login names of the users you want to give administrative priviliges on the CVS server.
          The file could look like this:
          cvsadmin
          charlie
          jennifer
          john

          Now each of these users are able to add new users, change their passwords and use the cvs admin command.

           

          Disabling the pserver protocol

          If you are exposing your CVSNT server to the Internet you should disable the :pserver: protocol because it uses too low security levels. Only the password for login is 'encrypted' and this is only barely so. All other traffic is in cleartext...
          To protect your data you should use the :sspi: protocol instead (and set its encryption flag of course).
          As an alternative with the same basic functionality as pserver you can use sserver instead. This uses encrypted connections by default and is probably better if you want to add cvs logins that do not correspond to real accounts (see above).
          Disabling any protocol on the CVSNT server is done through the CVSNT Control Panel Plugins tab.
          Select the :pserver: protocol line and click Configure. This will bring up a dialogue where you can just uncheck the checkbox to disable the protocol:

          Configuration screen #1


          Adding new pserver users using the cvs passwd command

          As soon as you have logged on using pserver or sserver with a cvs login name that is the same as a local system admin or is aliased to an admin account or is listed in the CVSROOT/admin file then you can add and delete CVS user logins with the passwd command. Here is the full syntax for this command:

          Usage:
          cvs passwd [-a] [-x] [-X] [-r real_user] [-R] [-D domain] [username]
          -a Add user
          -x Disable user
          -X Delete user
          -r Alias username to real system user
          -R Remove alias to real system user
          -D Use domain password

          Example:
          cvs passwd -r charlie -a john
          This adds a CVS login john with a system alias to account charlie. When the command is executed there will be a password dialogue that asks for the password of john twice for confirmation. Note that this is NOT the actual system password of account john, it is the CVS login password only used by CVSNT.
          After the command completes there will be a new line in the CVSROOT/passwd file looking somewhat like this:
          john:KacIT8t1F/SKU:charlie
          The part between the :: is the DES encrypted password you typed in and will be used by the CVSNT service during login to validate john. Once accepted the account charlie will instead be used so the password is no longer used. The CVSNT service has full priviliges to act on charlie's behalf and this is what it does too.

          Managing pserver and sserver users

          If you plan on using pserver or sserver with a fairly large number of different user logins then you might want to do as follows (also described above):

          • Create a local user on the CVSNT server by the name of "cvsuser".
          • Login to the cvs server using an admin account.
          • Add the logins with the following command to alias to the cvsuser:
            cvs passwd -r cvsuser -a <login user name>
            You will be asked twice for the login password.
          You may add as many pserver users this way as you like. They will all be individually identified by the login name even though the operations on the repository will be done in the cvsuser account context. Mail systems will recognize these user names as well (see below).

           

          Using the SSPI protocol for CVSNT access

          A few years ago the SSPI protocol was added to CVSNT. It works over TCP/IP so it can more easily traverse firewalls. Like :ntserver:, which is now depreciated, the :sspi: protocol does not need a login, instead the login you did when you started your workstation is used with this protocol.

          Limiting user access with sspi
          When used normally sspi will accept connections from all system users that authenticate against the system (local or domain). Often this is not really what we want, instead we want to use the same mechansism as is used with :pserver:. Here the CVSROOT/passwd file limits the logins accepted by CVSNT to those mentioned in the file.
          With :sspi: this is quite possible, you only have to list the account login names that you want to give CVS access in the passwd file. You also have to set the parameter
          SystemAuth = No
          in the CVSROOT/config file.
          Note that in this case there is no need for entering passwords into the passwd file, sspi uses the system login and the passwd file is only used as a list of accepted users. So simply issuing this command when logged in as a CVS administrator will work:
          cvs passwd -a newuser
          (press enter twice to tell CVSNT that no password is used)

          Fine-tuning user access of CVS

          The NTFS file system permissions can be used to tune the access to the CVS repository with more granularity than the passwd file allows. Here is how it is done:

          1. Create a number of NT user groups where members can be added and removed easily.
          2. Don't use aliases in the login scheme, let each user login as himself, for example using :sspi:.
          3. Set permissions (read/write, read only, no access) on the module level in the repository using the CVS groups as tokens.
          4. Give membership to the CVS user groups as needed to individual NT accounts

           

          Using spaces with CVSNT

          CVSNT tries its best to handle spaces embedded in file and directory names. But there still are instances where the use of spaces breaks the CVS functionality badly. So my recommendations are:

          1. Install CVSNT to a path that does not contain spaces.
          2. Place the repository on a path not containing spaces.
          3. If you install additional software like PERL or RCS, don't use spaces!
          4. Instruct your users not to use spaces in directory names that are to be handled by CVS.

          People may argue that CVSNT handles these issues for them, but in my experience this is only partly true. For example the loginfo and notify script parsing breaks fully when handling files with embedded spaces. There are other places as well...
          Also by allowing spaces you will make it impossible to later move the repository to a *nix system (Unix, Linux etc).
          So you are much better off prohibiting spaces up front!

          Afterwords

          This tutorial is written 2005-11-16 and is based on CVSNT version 2.5.03.2148.
          The test system is Windows Enterprise Server 2003 with SP1 installed running in Virtual PC 2004 SP1 on my development PC. The server is not member of a domain.

          Comments? Send me a message: Bo Berglund

          posted @ 2007-07-16 14:22 edsonjava 閱讀(2344) | 評(píng)論 (0)編輯 收藏
           

          2  Jive與設(shè)計(jì)模式

          Jive論壇系統(tǒng)使用大量設(shè)計(jì)模式巧妙地實(shí)現(xiàn)了一系列功能。因?yàn)樵O(shè)計(jì)模式的通用性和可理解性,將幫助更多人很快地理解 Jive論壇源碼,從而可以依據(jù)一種“協(xié)定”來動(dòng)態(tài)地?cái)U(kuò)展它。那么使用設(shè)計(jì)模式還有哪些好處?

          2.1  設(shè)計(jì)模式

          設(shè)計(jì)模式是一套被反復(fù)使用、多數(shù)人知曉的、經(jīng)過分類編目的、代碼設(shè)計(jì)經(jīng)驗(yàn)的總結(jié)。使用設(shè)計(jì)模式是為了可重用代碼、讓代碼更容易被他人理解、保證代碼可靠性。毫無疑問,設(shè)計(jì)模式于己于他人于系統(tǒng)都是多贏的。設(shè)計(jì)模式使代碼編制真正工程化,設(shè)計(jì)模式是軟件工程的基石。

          GOF(設(shè)計(jì)模式作者簡稱)《設(shè)計(jì)模式》這本書第一次將設(shè)計(jì)模式提升到理論高度,并將之規(guī)范化,該書提出了23種基本設(shè)計(jì)模式。自此,在可復(fù)用面向?qū)ο筌浖陌l(fā)展過程中,新的大量的設(shè)計(jì)模式不斷出現(xiàn)。

          很多人都知道Java是完全面向?qū)ο蟮脑O(shè)計(jì)和編程語言,但是由于接受教育以及經(jīng)驗(yàn)的原因,大多數(shù)程序員或設(shè)計(jì)人員都是從傳統(tǒng)的過程語言轉(zhuǎn)變而來,因此在思維習(xí)慣上要完全轉(zhuǎn)變?yōu)槊嫦驅(qū)ο蟮脑O(shè)計(jì)和開發(fā)方式是困難的,而學(xué)習(xí)設(shè)計(jì)模式可以更好地幫助和堅(jiān)固這種轉(zhuǎn)變。

          凡是學(xué)習(xí)完成設(shè)計(jì)模式的人都有一種類似重生的感覺,這種重生可以從很多方面去解釋。換一種新的角度來看待和解決問題應(yīng)該是一種比較貼切的解釋,而這種新的思維角度培養(yǎng)屬于基礎(chǔ)培訓(xùn),因此,設(shè)計(jì)模式是學(xué)習(xí)Java的必讀基礎(chǔ)課程之一。

          由于設(shè)計(jì)模式概念比較抽象,對(duì)于初學(xué)者學(xué)習(xí)有一定的難度,因此結(jié)合Jive論壇系統(tǒng)學(xué)習(xí)設(shè)計(jì)模式將是一種很好的選擇。

          掌握了設(shè)計(jì)模式,將會(huì)幫助程序員或設(shè)計(jì)人員以更加可重用性、可伸縮性的眼光來開發(fā)應(yīng)用系統(tǒng),甚至開發(fā)通用的框架系統(tǒng)。框架系統(tǒng)是構(gòu)成一類特定軟件可復(fù)用設(shè)計(jì)的一組相互協(xié)作的類,主要是對(duì)應(yīng)用系統(tǒng)中反復(fù)重用部分的提煉,類似一種模板,這是一種結(jié)構(gòu)性的模板。

          框架通常定義了應(yīng)用體系的整體結(jié)構(gòu)、類和對(duì)象的關(guān)系等設(shè)計(jì)參數(shù),以便于具體應(yīng)用實(shí)現(xiàn)者能集中精力于應(yīng)用本身的特定細(xì)節(jié)。框架強(qiáng)調(diào)設(shè)計(jì)復(fù)用,而設(shè)計(jì)模式最小的可重用單位,因此框架不可避免地會(huì)反復(fù)使用到設(shè)計(jì)模式。關(guān)于通用框架系統(tǒng)的設(shè)計(jì)開發(fā)將在以后章節(jié)中討論。

          其實(shí)Jive論壇本身也形成了一個(gè)基于Web結(jié)構(gòu)的通用框架系統(tǒng),因?yàn)樗芏嘣O(shè)計(jì)思想是可以重用的,例如設(shè)定 一個(gè)總體入口,通過入口檢查用戶的訪問控制權(quán)限,當(dāng)然還有其他各方面的功能實(shí)現(xiàn)方式都是值得在其他系統(tǒng)中借鑒的,也正因?yàn)樗阅J降男问奖憩F(xiàn)出來,這種可 重用性和可借鑒性就更強(qiáng)。

          2.2  ForumFactory與工廠模式

          工廠模式是GOF設(shè)計(jì)模式的主要常用模式,它主要是為創(chuàng)建對(duì)象提供了一種接口,工廠模式主要是封裝了創(chuàng)建對(duì)象的細(xì)節(jié)過程,從而使得外界調(diào)用一個(gè)對(duì)象時(shí),根本無需關(guān)心這個(gè)對(duì)象是如何產(chǎn)生的。

          在GOF設(shè)計(jì)模式中,工廠模式分為工廠方法模式和抽象工廠模式。兩者主要區(qū)別是,工廠方法是創(chuàng)建一種產(chǎn)品接口下的產(chǎn)品對(duì)象,而抽象工廠模式是創(chuàng)建多種產(chǎn)品接口下的產(chǎn)品對(duì)象,非常類似Builder生成器模式。在平時(shí)實(shí)踐中,使用較多的基本是工廠方法模式。

          以類SampleOne為例,要?jiǎng)?chuàng)建SampleOne的對(duì)象實(shí)例:

          SampleOne sampleOne = new SampleOne();

          如果Sample類有幾個(gè)相近的類:SampleTwo或SampleThree,那么創(chuàng)建它們的實(shí)例分別是:

          SampleTwo sampleTwo = new SampleTwo();

          SampleThree sampleThree = new SampleThree();

          其實(shí)這3個(gè)類都有一些共同的特征,如網(wǎng)上商店中銷售書籍、玩具或者化妝品。雖然它們是不同的具體產(chǎn)品,但是它 們有一個(gè)共同特征,可以抽象為“商品”。日常生活中很多東西都可以這樣高度抽象成一種接口形式。上面這3個(gè)類如果可以抽象為一個(gè)統(tǒng)一接口 SampleIF,那么上面語句就可以成為:

          SampleIF sampleOne = new SampleOne();

          SampleIF sampleTwo = new SampleTwo();

          SampleIF sampleThree = new SampleThree();

          在實(shí)際情況中,有時(shí)并不需要同時(shí)生成3種對(duì)象,而是根據(jù)情況在3者之中選一個(gè)。在這種情況下,需要使用工廠方法來完成了,創(chuàng)建一個(gè)叫SampleFactory的抽象類:

          public class SampleFactory{

             public abstract SampleIF creator();

          }

          在這個(gè)抽象工廠類中有一個(gè)抽象方法creator,但是沒有具體實(shí)現(xiàn),而是延遲到它的子類中實(shí)現(xiàn),創(chuàng)建子類SampleFactoryImp:

          public class SampleFactoryImp extends SampleFactory{

             public SampleIF creator(){

              //根據(jù)其他因素綜合判斷返回具體產(chǎn)品

              //假設(shè)應(yīng)該返回SampleOne對(duì)象

                 return new SampleOne();

          }

          }

          在SampleFactoryImp中根據(jù)具體情況來選擇返回SampleOne、SampleTwo或SampleThree。所謂具體情況有很多種:上下文其他過程計(jì)算結(jié)果;直接根據(jù)配置文件中配置。

          上述工廠方法模式中涉及到一個(gè)抽象產(chǎn)品接口Sample,如果還有其他完全不同的產(chǎn)品接口,如Product 等,一個(gè)子類SampleFactoryImp只能實(shí)現(xiàn)一套系列產(chǎn)品方案的生產(chǎn),如果還需要另外一套系統(tǒng)產(chǎn)品方案,就可能需要另外一個(gè)子類 SampleFactoryImpTwo來實(shí)現(xiàn)。這樣,多個(gè)產(chǎn)品系列、多個(gè)工廠方法就形成了抽象工廠模式。

          前面已經(jīng)討論在Jive中設(shè)置了論壇統(tǒng)一入口,這個(gè)統(tǒng)一入口就是ForumFactory,以下是ForumFactory的主要代碼:

          public abstract class ForumFactory {

            private static Object initLock = new Object();

            private static String className = " com.Yasna.forum.database.DbForumFactory";

            private static ForumFactory factory = null;

            

            public static ForumFactory getInstance(Authorization authorization) {

              if (authorization == null) {

                return null;

              }

              //以下使用了Singleton 單態(tài)模式,將在2.3節(jié)討論

              if (factory == null) {

                synchronized(initLock) {

                  if (factory == null) {

                      ... //從配置文件中獲得當(dāng)前className

                    try {

                        //動(dòng)態(tài)裝載類

                        Class c = Class.forName(className);

                        factory = (ForumFactory)c.newInstance();

                    }

                    catch (Exception e) {

                        return null;

                    }

                  }

                }

              }

              //返回 proxy.用來限制授權(quán)對(duì)forum的訪問

              return new ForumFactoryProxy(authorization, factory,factory.getPermissions(authorization));

            }

            //創(chuàng)鍵產(chǎn)品接口Forum的具體對(duì)象實(shí)例

            public abstract Forum createForum(String name, String description)

            throws UnauthorizedException, ForumAlreadyExistsException;

             //創(chuàng)鍵產(chǎn)品接口ForumThread的具體對(duì)象實(shí)例

          public abstract ForumThread createThread(ForumMessage rootMessage)

          throws UnauthorizedException;

          //創(chuàng)鍵產(chǎn)品接口ForumMessage的具體對(duì)象實(shí)例

           

           

              public abstract ForumMessage createMessage();

            ....

          }

          ForumFactory中提供了很多抽象方法如createForum、createThread和 createMessage()等,它們是創(chuàng)建各自產(chǎn)品接口下的具體對(duì)象,這3個(gè)接口就是前面分析的基本業(yè)務(wù)對(duì)象Forum、ForumThread和 ForumMessage,這些創(chuàng)建方法在ForumFactory中卻不立即執(zhí)行,而是推遲到ForumFactory子類中實(shí)現(xiàn)。

          ForumFactory的子類實(shí)現(xiàn)是 com.Yasna.forum.database.DbForumFactory,這是一種數(shù)據(jù)庫實(shí)現(xiàn)方式。即在DbForumFactory中分別實(shí) 現(xiàn)了在數(shù)據(jù)庫中createForum、createThread和createMessage()等3種方法,當(dāng)然也提供了動(dòng)態(tài)擴(kuò)展到另外一套系列產(chǎn)品 的生產(chǎn)方案的可能。如果使用XML來實(shí)現(xiàn),那么可以編制一個(gè)XmlForumFactory的具體工廠子類來分別實(shí)現(xiàn)3種創(chuàng)建方法。

          因此,Jive論壇在統(tǒng)一入口處使用了抽象工廠模式來動(dòng)態(tài)地創(chuàng)建論壇中所需要的各種產(chǎn)品,如圖3-4所示。

          圖3-4  ForumFactory抽象工廠模式圖

          圖3-4中,XmlForumFactory和DbForumFactory作為抽象工廠 ForumFactory的兩個(gè)具體實(shí)現(xiàn),而Forum、ForumThread和ForumMessage分別作為3個(gè)系列抽象產(chǎn)品接口,依靠不同的工 廠實(shí)現(xiàn)方式,會(huì)產(chǎn)生不同的產(chǎn)品對(duì)象。

          從抽象工廠模式去理解Jive論壇統(tǒng)一入口處,可以一步到位掌握了幾個(gè)類之間的大概關(guān)系。因?yàn)槭褂昧顺橄蠊S模式這種通用的設(shè)計(jì)模式,可以方便源碼閱讀者快速地掌握整個(gè)系統(tǒng)的結(jié)構(gòu)和來龍去脈,圖3-4這張圖已經(jīng)初步展示了Jive的主要框架結(jié)構(gòu)。

          細(xì)心的讀者也許會(huì)發(fā)現(xiàn),在上面ForumFactory有一個(gè)getInstance比較令人費(fèi)解,這將在2.3節(jié)進(jìn)行討論。


          2.3  統(tǒng)一入口與單態(tài)模式

          在上面ForumFactory的getInstance方法使用單態(tài)(SingleTon)模式。單態(tài)模式是保證一個(gè)類有且僅有一個(gè)對(duì)象實(shí)例,并提供一個(gè)訪問它的全局訪問點(diǎn)。

          前面曾提到ForumFactory是Jive提供客戶端訪問數(shù)據(jù)庫系統(tǒng)的統(tǒng)一入口。為了保證所有的客戶端請(qǐng)求都要經(jīng)過這個(gè)ForumFactory,如果不使用單態(tài)模式,客戶端下列調(diào)用語句表示生成了ForumFactory實(shí)例:

          ForumFactory factory = new DbForumFactory();

          客戶端每發(fā)生一次請(qǐng)求都調(diào)用這條語句,這就會(huì)發(fā)生每次都生成不同factory對(duì)象實(shí)例,這顯然不符合設(shè)計(jì)要求,因此必須使用單態(tài)模式。

          一般在Java實(shí)現(xiàn)單態(tài)模式有幾種選擇,最常用而且安全的用法如下:

          public class Singleton {

            private Singleton(){}

            //在自己內(nèi)部定義自己一個(gè)實(shí)例,是不是很奇怪

            //注意這是private,只供內(nèi)部調(diào)用

            private static Singleton instance = new Singleton();

            //這里提供了一個(gè)供外部訪問本class的靜態(tài)方法,可以直接訪問

            public static Singleton getInstance() {

              return instance;

            }

          }

          單態(tài)模式一共使用了兩條語句實(shí)現(xiàn):第一條直接生成自己的對(duì)象,第二條提供一個(gè)方法供外部調(diào)用這個(gè)對(duì)象,同時(shí)最好將構(gòu)造函數(shù)設(shè)置為private,以防止其他程序員直接使用new Singleton生成實(shí)例。

          還有一種Java單態(tài)模式實(shí)現(xiàn):

          public class Singleton {

            private Singleton(){}

            private static Singleton instance = null;

            public static synchronized Singleton getInstance() {

              if (instance==null)

                instance=new Singleton()

              return instance;

            }

          在上面代碼中,使用了判斷語句。如果instance為空,再進(jìn)行實(shí)例化,這成為lazy initialization。注意getInstance()方法的synchronized,這個(gè)synchronized很重要。如果沒有 synchronized,那么使用getInstance()在第一次被訪問時(shí)有可能得到多個(gè)Singleton實(shí)例。

          關(guān)于lazy initialization的Singleton有很多涉及double-checked locking (DCL)的討論,有興趣者可以進(jìn)一步研究。一般認(rèn)為第一種形式要更加安全些;但是后者可以用在類初始化時(shí)需要參數(shù)輸入的情況下。

          在Jive的ForumFactory中采取了后者lazy initialization形式,這是為了能夠動(dòng)態(tài)配置指定ForumFactory的具體子類。在getInstance中,從配置文件中獲得當(dāng)前工 廠的具體實(shí)現(xiàn),如果需要啟動(dòng)XmlForumFactory,就不必修改ForumFactory代碼,直接在配置文件中指定className的名字為 XmlForumFactory。這樣通過下列動(dòng)態(tài)裝載機(jī)制生成ForumFactory具體對(duì)象:

          Class c = Class.forName(className);

          factory = (ForumFactory)c.newInstance();

          這是利用Java的反射機(jī)制,可以通過動(dòng)態(tài)指定className的數(shù)值而達(dá)到生成對(duì)象的方式。

          使用單態(tài)模式的目標(biāo)是為了控制對(duì)象的創(chuàng)建,單態(tài)模式經(jīng)常使用在控制資源的訪問上。例如數(shù)據(jù)庫連接或 Socket連接等。單態(tài)模式可以控制在某個(gè)時(shí)刻只有一個(gè)線程訪問資源。由于Java中沒有全局變量的概念,因此使用單態(tài)模式有時(shí)可以起到這種作用,當(dāng)然 需要注意是在一個(gè)JVM中。

          2.4  訪問控制與代理模式

          仔細(xì)研究會(huì)發(fā)現(xiàn),在ForumFactory的getInstance方法中最后的返回值有些奇怪。按照單態(tài) 模式的概念應(yīng)該直接返回factory這個(gè)對(duì)象實(shí)例,但是卻返回了ForumFactoryProxy的一個(gè)實(shí)例,這實(shí)際上改變了單態(tài)模式的初衷。這樣客 戶端每次通過調(diào)用ForumFactory的getInstance返回的就不是ForumFactory的惟一實(shí)例,而是新的對(duì)象。之所以這樣做是為了 訪問權(quán)限的控制,姑且不論這樣做的優(yōu)劣,先看看什么是代理模式。

          代理模式是屬于設(shè)計(jì)模式結(jié)構(gòu)型模式中一種,它是實(shí)際訪問對(duì)象的代理對(duì)象,或者影子對(duì)象,主要達(dá)到控制實(shí)際對(duì)象的訪問。這種控制的目的很多,例如提高性能等。即遠(yuǎn)程代理模式,這種模式將在以后章節(jié)討論。

          其中一個(gè)主要的控制目的是控制客戶端對(duì)實(shí)際對(duì)象的訪問權(quán)限。在Jive系統(tǒng)中,因?yàn)橛薪巧珯?quán)限的分別,對(duì)于Forum、ForumThread和FroumMessage的訪問操作必須經(jīng)過權(quán)限機(jī)制驗(yàn)證后才能進(jìn)行。

          以ForumFactoryProxy中的createForum方法為例,其實(shí)ForumFactoryProxy也是FroumFactory的一種工廠實(shí)現(xiàn),它的createForum具體實(shí)現(xiàn)如下:

          public Forum createForum(String name, String description)

                      throws UnauthorizedException, ForumAlreadyExistsException

              {

                  if (permissions.get(ForumPermissions.SYSTEM_ADMIN)) {

                      Forum newForum = factory.createForum(name, description);

                      return new ForumProxy(newForum, authorization, permissions);

                  }

                  else {

                      throw new UnauthorizedException();

                  }

          }

          在這個(gè)方法中進(jìn)行了權(quán)限驗(yàn)證,判斷是否屬于系統(tǒng)管理員。如果是,將直接從DbForumFactory對(duì)象 factory的方法createForum中獲得一個(gè)新的Forum對(duì)象,然后再返回Forum的子類代理對(duì)象ForumProxy。因?yàn)樵贔orum 中也還有很多屬性和操作方法,這些也需要進(jìn)行權(quán)限驗(yàn)證。ForumProxy和ForumFactoryProxy起到類似的作用。

          Jive中有下列幾個(gè)代理類:

          ·          ForumFactoryProxy:客戶端和DbForumFactory之間的代理。客戶端訪問DbForumFactory的任何方法都要先經(jīng)過ForumFactoryProxy相應(yīng)方法代理一次。以下意思相同。

          ·          ForumProxy:客戶端和DbForum之間的代理,研究Forum對(duì)象的每個(gè)方法,必須先看ForumProxy對(duì)象的方法。

          ·          ForumMessageProxy:客戶端和DbForumMessage之間的代理。

          ·          ForumThreadProxy:客戶端和DbForumThread之間的代理。

          User和Group也有相應(yīng)的代理類。

          由以上分析看出,每個(gè)數(shù)據(jù)對(duì)象都有一個(gè)代理。如果系統(tǒng)中數(shù)據(jù)對(duì)象非常多,依據(jù)這種一對(duì)一的代理關(guān)系,會(huì)有很多代理類,將使系統(tǒng)變得不是非常干凈,因此可以使用動(dòng)態(tài)代理來代替這所有的代理類,具體實(shí)現(xiàn)將在以后章節(jié)討論。

          2.5  批量分頁查詢與迭代模式

          迭代(Iterator)模式是提供一種順序訪問某個(gè)集合各個(gè)元素的方法,確保不暴露該集合的內(nèi)部表現(xiàn)。迭代模式應(yīng)用于對(duì)大量數(shù)據(jù)的訪問,Java Collection API中Iterator就是迭代模式的一種實(shí)現(xiàn)。

          在前面章節(jié)已經(jīng)討論過,用戶查詢大量數(shù)據(jù),從數(shù)據(jù)庫不應(yīng)該直接返回ResultSet,應(yīng)該是 Collection。但是有一個(gè)問題,如果這個(gè)數(shù)據(jù)很大,需要分頁面顯示。如果一下子將所有頁面要顯示的數(shù)據(jù)都查詢出來放在Collection,會(huì)影 響性能。而使用迭代模式則不必將全部集合都展現(xiàn)出來,只有遍歷到某個(gè)元素時(shí)才會(huì)查詢數(shù)據(jù)庫獲得這個(gè)元素的數(shù)據(jù)。

          以論壇中顯示帖子主題為例,在一個(gè)頁面中不可能顯示所有主題,只有分頁面顯示,如圖3-5所示。

          圖3-5中一共分15頁來顯示所有論壇帖子,可以從顯示Forum.jsp中發(fā)現(xiàn)下列語句可以完成上述結(jié)果:

          ResultFilter filter = new ResultFilter();  //設(shè)置結(jié)果過濾器

             filter.setStartIndex(start);             //設(shè)置開始點(diǎn)

             filter.setNumResults(range);          //設(shè)置范圍

             ForumThreadIterator threads = forum.threads(filter);  //獲得迭代器

             while(threads.hasNext){

                 //逐個(gè)顯示threads中帖子主題,輸出圖3-5中的每一行

             }

          圖3-5  分頁顯示所有帖子

          上述代碼中主要是從Forum的threads方法獲得迭代器ForumThreadIterator的實(shí) 例,依據(jù)前面代理模式中分析、研究Forum對(duì)象的方法,首先是看ForumProxy中對(duì)應(yīng)方法,然后再看DbForum中對(duì)應(yīng)方法的具體實(shí)現(xiàn)。在 ForumProxy中,threads方法如下:

          public ForumThreadIterator threads(ResultFilter resultFilter) {

               ForumThreadIterator iterator = forum.threads(resultFilter);

                return new ForumThreadIteratorProxy(iterator, authorization, permissions);

          }

          首先是調(diào)用了DbForum中具體的threads方法,再追蹤到DbForum中看看,它的threads方法代碼如下:

          public ForumThreadIterator threads(ResultFilter resultFilter) {

          //按resultFilter設(shè)置范圍要求獲得SQL查詢語句

             String query = getThreadListSQL(resultFilter, false); 

             //獲得resultFilter設(shè)置范圍內(nèi)的所有ThreadID集合

             long [] threadBlock = getThreadBlock(query.toString(), resultFilter.getStartIndex());

             //以下是計(jì)算查詢區(qū)域的開始點(diǎn)和終點(diǎn)

             int startIndex = resultFilter.getStartIndex();

             int endIndex;

             // If number of results is set to inifinite, set endIndex to the total

            // number of threads in the forum.

             if (resultFilter.getNumResults() == ResultFilter.NULL_INT) {

               endIndex = (int)getThreadCount(resultFilter);

             }else {

               endIndex = resultFilter.getNumResults() + startIndex;

             }

             return new ForumThreadBlockIterator(threadBlock, query.toString(),

                         startIndex, endIndex, this.id, factory);

          }

          ResultFilter是一個(gè)查詢結(jié)果類,可以對(duì)論壇主題Thread和帖子內(nèi)容Message進(jìn)行過濾或 排序,這樣就可以根據(jù)用戶要求定制特殊的查詢范圍。如查詢某個(gè)用戶去年在這個(gè)論壇發(fā)表的所有帖子,那只要?jiǎng)?chuàng)建一個(gè)ResultFilter對(duì)象就可以代表 這個(gè)查詢要求。

          在上面threads方法代碼中,第一步是先定制出相應(yīng)的動(dòng)態(tài)SQL查詢語句,然后使用這個(gè)查詢語句查詢數(shù)據(jù)庫,獲得查詢范圍內(nèi)所有的ForumThread的ID集合,然后在這個(gè)ID集合中獲得當(dāng)前頁面的ID子集合,這是非常關(guān)鍵的一步。

          在這關(guān)鍵的一步中,有兩個(gè)重要的方法getThreadListSQL和getThreadBlock:

          ·          GetThreadListSQL:獲得SQL查詢語句query的值,這個(gè)方法Jive實(shí)現(xiàn)起來顯得非常地瑣碎。

          ·          GetThreadBlock:獲得當(dāng)前頁面的ID子集合,那么如何確定ID子集合的開始位置呢?查看getThreadBlock方法代碼,可以發(fā)現(xiàn),它是使用最普遍的ResultSet next()方法來逐個(gè)跳躍到開始位置。

          上面代碼的Threads方法中最后返回的是ForumThreadBlockIterator,它是抽象類 ForumThreadIterator的子類,而ForumThreadIterator繼承了Collection的Iterator,以此聲明自己 是一個(gè)迭代器,F(xiàn)orumMessageBlockIterator實(shí)現(xiàn)的具體方法如下:

          public boolean hasNext();     //判斷是否有下一個(gè)元素

          public boolean hasPrevious()  //判斷是否有前一個(gè)元素

          public Object next() throws java.util.NoSuchElementException  //獲得下一個(gè)元素實(shí)例

          ForumThreadBlockIterator中的Block是“頁”的意思,它的一個(gè)主要類變量 threadBlock包含的是一個(gè)頁面中所有ForumThread的ID,next()方法實(shí)際是對(duì)threadBlock中ForumThread 進(jìn)行遍歷,如果這個(gè)頁面全部遍歷完成,將再獲取下一頁(Block)數(shù)據(jù)。

          在ForumThreadBlockIterator重要方法getElement中實(shí)現(xiàn)了兩個(gè)功能:

          ·          如果當(dāng)前遍歷指針超過當(dāng)前頁面,將使用getThreadBlock獲得下一個(gè)頁面的ID子集合;

          ·          如果當(dāng)前遍歷指針在當(dāng)前頁面之內(nèi),根據(jù)ID獲得完整的數(shù)據(jù)對(duì)象,實(shí)現(xiàn)輸出;

          ForumThreadBlockIterator的getElement方法代碼如下:

          private Object getElement(int index) {

             if (index < 0) {        return null;        }

             // 檢查所要獲得的 element 是否在本查詢范圍內(nèi)(當(dāng)前頁面內(nèi))

             if (index < blockStart ||

          index >= blockStart + DbForum.THREAD_BLOCK_SIZE) {  

                try {

                    //從緩沖中獲得Forum實(shí)例

                    DbForum forum = factory.cacheManager.forumCache.get(forumID);

                    //獲得下一頁的內(nèi)容

                    this.threadBlock = forum.getThreadBlock(query, index);

                    this.blockID = index / DbForum.THREAD_BLOCK_SIZE;

                    this.blockStart = blockID * DbForum.THREAD_BLOCK_SIZE;

                } catch (ForumNotFoundException fnfe) {

                         return null;

                 }

               }

               Object element = null;

               // 計(jì)算這個(gè)元素在當(dāng)前查詢范圍內(nèi)的相對(duì)位置

               int relativeIndex = index % DbForum.THREAD_BLOCK_SIZE;

               // Make sure index isn't too large

               if (relativeIndex < threadBlock.length) {

                  try {

                      // 從緩沖中獲得實(shí)際thread 對(duì)象

                      element = factory.cacheManager.threadCache.get(

                                  threadBlock[relativeIndex]);

                  } catch (ForumThreadNotFoundException tnfe) { }

               }

               return element;

          }

          ForumThreadBlockIterator是真正實(shí)現(xiàn)分頁查詢的核心功能, ForumThreadBlockIterator對(duì)象返回到客戶端的過程中,遭遇ForumThreadIteratorProxy的截獲,可以回頭看 看ForumProxy中的threads方法,它最終返回給調(diào)用客戶端Forum.jsp的是ForumThreadIteratorProxy實(shí)例。

          ForumThreadIteratorProxy也是迭代器ForumThreadIterator的一個(gè)子類,它的一個(gè)具體方法中:

          public Object next() {

            return new ForumThreadProxy((ForumThread)iterator.next(), authorization,

                      permissions);

          }

          這一句是返回一個(gè)ForumThreadProxy實(shí)例,返回就是一個(gè)ForumThread實(shí)例的代理。這里,Jive使用代理模式實(shí)現(xiàn)訪問控制實(shí)現(xiàn)得不是很巧妙,似乎有代理到處“飛”的感覺,這是可以對(duì)之進(jìn)行改造的。

          從以上可以看出,Jive在輸出如圖3-5所示的多頁查詢結(jié)果時(shí),采取了下列步驟:

          (1)先查詢出符合查詢條件的所有對(duì)象元素的ID集合,注意不是所有對(duì)象元素,只是其ID的集合,這樣節(jié)約了大量內(nèi)存。

          (2)每個(gè)頁面視為一個(gè)Block,每當(dāng)進(jìn)入下一頁時(shí),獲得下一個(gè)頁面的所有對(duì)象的ID集合。

          (3)輸出當(dāng)前頁面的所有對(duì)象時(shí),首先從緩沖中獲取,如果緩沖中沒有,再根據(jù)ID從數(shù)據(jù)庫中獲取完整的對(duì)象數(shù)據(jù)。

          上述實(shí)現(xiàn)方法完全基于即查即顯,相比于一般批量查詢做法:一次性獲得所有數(shù)據(jù),然后遍歷數(shù)據(jù)結(jié)果集ResultSet,Jive這種批量查詢方式是一種比較理想的選擇。

          以上是ForumThread的批量顯示,有關(guān)帖子內(nèi)容ForumMessage也是采取類似做法。在每個(gè) ForumThread中可能有很多帖子內(nèi)容(ForumMessage對(duì)象集合),也不能在一個(gè)頁面中全部顯示,所以也是使用迭代模式來實(shí)現(xiàn)的。顯示一 個(gè)Forum主題下所有帖子內(nèi)容的功能由ForumThread的messages()方法完成,檢查它的代理類FroumThreadProxy如何具 體完成:

          public Iterator messages(ResultFilter resultFilter) {

             Iterator iterator = thread.messages(resultFilter);

             return new IteratorProxy(JiveGlobals.MESSAGE, iterator, authorization, permissions);

          }

          實(shí)現(xiàn)的原理基本相同,返回的都是一個(gè)Iterator代理類,在這些代理類中都是進(jìn)行用戶權(quán)限檢驗(yàn)的。

          Jive中也有關(guān)于一次性獲得所有數(shù)據(jù),然后遍歷ResultSet的做法。這種做法主要適合一次性查詢數(shù)據(jù)庫的所有數(shù)據(jù),例如查詢當(dāng)前所有論壇Forum,首先實(shí)現(xiàn)SQL語句:

          SELECT forumID FROM jiveForum

          獲得所有Forum的forumID,這段代碼位于DbForumFactory.java的forums方法中,如下:

            public Iterator forums() {

              if (forums == null) {

                LongList forumList = new LongList();

                Connection con = null;

                PreparedStatement pstmt = null;

                try {

                  con = ConnectionManager.getConnection();

                  // GET_FORUMS值是SELECT forumID FROM jiveForum

                  pstmt = con.prepareStatement(GET_FORUMS);

                  ResultSet rs = pstmt.executeQuery();

                  while (rs.next()) {

                    forumList.add(rs.getLong(1));                 //將所有查詢ID結(jié)果放入forumList中

                  }

                }catch (SQLException sqle) {

                  sqle.printStackTrace();

                } finally {

                  …

              }

              return new DatabaseObjectIterator(JiveGlobals.FORUM, forums, this);

            }

          forums方法是返回一個(gè)DatabaseObjectIterator,這個(gè) DatabaseObjectIterator也是一個(gè)迭代器,但是實(shí)現(xiàn)原理要比ForumThreadBlockIterator簡單。它只提供了一個(gè) 遍歷指針,在所有ID結(jié)果集中遍歷,然后也是通過ID獲得完整的數(shù)據(jù)對(duì)象。

          總之,Jive中關(guān)于批量查詢有兩種實(shí)現(xiàn)方式:以ForumThreadBlockIterator為代表的實(shí)現(xiàn)方式適合在數(shù)據(jù)量巨大、需要多頁查詢時(shí)使用;而DatabaseObjectIterator則是推薦在一個(gè)頁面中顯示少量數(shù)據(jù)時(shí)使用。

          2.6  過濾器與裝飾模式

          裝飾(Decorator)模式是動(dòng)態(tài)給一個(gè)對(duì)象添加一些額外的職責(zé),或者說改變這個(gè)對(duì)象的一些行為。這就類似于使用油漆為某個(gè)東西刷上油漆,在原來的對(duì)象表面增加了一層外衣。

          在裝飾模式中,有兩個(gè)主要角色:一個(gè)是被刷油漆的對(duì)象(decoratee);另外一個(gè)是給decoratee刷油漆的對(duì)象(decorator)。這兩個(gè)對(duì)象都繼承同一個(gè)接口。

          首先舉一個(gè)簡單例子來說明什么是裝飾模式。

          先創(chuàng)建一個(gè)接口:

          public interface Work

          {

            public void insert();

          }

          這是一種打樁工作的抽象接口,動(dòng)作insert表示插入,那么插入什么?下面這個(gè)實(shí)現(xiàn)表示方形木樁的插入:

          public class SquarePeg implements Work{

            public void insert(){

              System.out.println("方形樁插入");

            }

          }

          本來這樣也許就可以滿足打樁的工作需要,但是有可能土質(zhì)很硬,在插入方形樁之前先要打一個(gè)洞,那么又將如何實(shí)現(xiàn)?可以編制一個(gè)Decorator類,同樣繼承Work接口,但是在實(shí)現(xiàn)insert方法時(shí)有些特別:

          public class Decorator implements Work{

            private Work work;

            //額外增加的功能被打包在這個(gè)List中

            private ArrayList others = new ArrayList();

            public Decorator(Work work)

            {

              this.work=work;

              others.add("打洞");   //準(zhǔn)備好額外的功能

            }

            public void insert(){

              otherMethod();

              work.insert();

            }

            public void otherMethod()

            {

              ListIterator listIterator = others.listIterator();

              while (listIterator.hasNext())

              {

                System.out.println(((String)(listIterator.next())) + " 正在進(jìn)行");

              }

          }

          }

          在Decorator的方法insert中先執(zhí)行otherMethod()方法,然后才實(shí)現(xiàn)SquarePeg的insert方法。油漆工Decorator給被油漆者SquarePeg添加了新的行為——打洞。具體客戶端調(diào)用如下:

          Work squarePeg = new SquarePeg();

          Work decorator = new Decorator(squarePeg);

          decorator.insert();

          本例中只添加了一個(gè)新的行為(打洞),如果還有很多類似的行為,那么使用裝飾模式的優(yōu)點(diǎn)就體現(xiàn)出來了。因?yàn)榭梢酝ㄟ^另外一個(gè)角度(如組織新的油漆工實(shí)現(xiàn)子類)來對(duì)這些行為進(jìn)行混合和匹配,這樣就不必為每個(gè)行為創(chuàng)建一個(gè)類,從而減少了系統(tǒng)的復(fù)雜性。

          使用裝飾模式可以避免在被油漆對(duì)象decoratee中包裝很多動(dòng)態(tài)的,可能需要也可能不需要的功能,只要在系統(tǒng)真正運(yùn)行時(shí),通過油漆工decorator來檢查那些需要加載的功能,實(shí)行動(dòng)態(tài)加載。

          Jive論壇實(shí)現(xiàn)了信息過濾功能。例如可以將帖子內(nèi)容中的HTML語句過濾掉;可以將帖子內(nèi)容中Java代碼 以特別格式顯示等。這些過濾功能有很多,在實(shí)際使用時(shí)不一定都需要,是由實(shí)際情況選擇的。例如有的論壇就不需要將帖子內(nèi)容的HTML語句過濾掉,選擇哪些 過濾功能是由論壇管理者具體動(dòng)態(tài)決定的。而且新的過濾功能可能隨時(shí)可以定制開發(fā)出來,如果試圖強(qiáng)行建立一種接口包含所有過濾行為,那么到時(shí)有新過濾功能加 入時(shí),還需要改變接口代碼,真是一種危險(xiǎn)的行為。

          裝飾模式可以解決這種運(yùn)行時(shí)需要?jiǎng)討B(tài)增加功能的問題,且看看Jive是如何實(shí)現(xiàn)的。

          前面討論過,在Jive中,有主要幾個(gè)對(duì)象ForumFactory、Forum以及ForumThread 和ForumMessage,它們之間的關(guān)系如圖3-2所示。因此帖子內(nèi)容ForumMessage對(duì)象的獲得是從其上級(jí)FroumThread的方法 getMessage中獲取,但是在實(shí)際代碼中,F(xiàn)orumThread的方法getMessage委托ForumFactory來獲取 ForumMessage對(duì)象。看看ForumThread的子類DbForumThread的getMessage代碼:

          public ForumMessage getMessage(long messageID)

                   throws ForumMessageNotFoundException

          {

               return factory.getMessage(messageID, this.id, forumID);

          }

          這是一種奇怪的委托,大概是因?yàn)樾枰紤]到過濾器功能有意為之吧。那就看看ForumFactory的具體實(shí) 現(xiàn)子類DbForumFactory的getMessage功能,getMessage是將數(shù)據(jù)庫中的ForumMessage對(duì)象經(jīng)由過濾器過濾一遍后 輸出(注:因?yàn)樵瓉淼腏ive的getMessage代碼考慮到可緩存或不可緩存的過濾,比較復(fù)雜,實(shí)際過濾功能都是可以緩存的,因此精簡如下)。

          protected ForumMessage getMessage(long messageID, long threadID, long forumID)

                      throws ForumMessageNotFoundException

          {

                  DbForumMessage message = cacheManager.messageCache.get(messageID);

                  // Do a security check to make sure the message comes from the thread.

                  if (message.threadID != threadID) {

                      throw new ForumMessageNotFoundException();

                  }

                  ForumMessage filterMessage = null;

                      try {

            // 應(yīng)用全局過濾器

               filterMessage = filterManager.applyFilters(message);

                          Forum forum = getForum(forumID);               

                          //應(yīng)用本論壇過濾器

                          filterMessage = forum.getFilterManager().applyFilters(filterMessage);

                      }

                      catch (Exception e) { }

                  return filterMessage;

          }

          上面代碼實(shí)際是裝飾模式的客戶端調(diào)用代碼,DbForumMessage 的實(shí)例message是被油漆者decoratee。通過filterManager 或forum.getFilterManager()的applyFilter方法,將message實(shí)行了所有的過濾功能。這就類似前面示例的下列語 句:

          Work decorator = new Decorator(squarePeg);

          forum.getFilterManager()是從數(shù)據(jù)庫中獲取當(dāng)前配置的所有過濾器類。每個(gè)Forum都有一套自己的過濾器類,這是通過下列語句實(shí)現(xiàn)的:

          FilterManager filterManager =  new DbFilterManager();

          在DbFilterManager 的類變量ForumMessageFilter [] filters中保存著所有的過濾器,applyFilters方法實(shí)行過濾如下:

          public ForumMessage applyFilters(ForumMessage message) {

              for (int i=0; i < filters.length; i++) {

                   if (filters[i] != null) {

                      message = filters[i].clone(message);

                   }

               }

               return message;

          }

          而ForumMessageFilter是ForumMessage的另外一個(gè)子類,被油漆者DbForumMessage通過油漆工ForumMessageFilter增加了一些新的行為和功能(過濾),如圖3-6所示。

          圖3-6  裝飾模式

          這就組成了一個(gè)稍微復(fù)雜一點(diǎn)的裝飾模式。HTMLFilter實(shí)現(xiàn)了HTML代碼過濾功能,而JavaCodeHighLighter實(shí)現(xiàn)了Java代碼過濾功能,HTMLFilter代碼如下:

          public class HTMLFilter extends ForumMessageFilter {

              public ForumMessageFilter clone(ForumMessage message){

                  HTMLFilter filter = new HTMLFilter();

                  filter.message = message;

                  return filter;

              }

              public boolean isCacheable() {

                  return true;

              }

              public String getSubject() {

                  return StringUtils.escapeHTMLTags(message.getSubject());

              }

              public String getBody() {

                  return StringUtils.escapeHTMLTags(message.getBody());

              }

          }

          HTMLFilter中重載了ForumMessage的getSubject()、getBody()方法,實(shí)際是改變了這兩個(gè)原來的行為,這類似前面舉例的方法:

          public void insert(){

              otherMethod();

              work.insert();

          }

          這兩者都改變了被油漆者的行為。

          在HTMLFilter中還使用了原型(Prototype)模式,原型模式定義是:用原型實(shí)例指定創(chuàng)建對(duì)象的種類,并且通過復(fù)制這些原型創(chuàng)建新的對(duì)象。按照這種定義,Java的clone技術(shù)應(yīng)該是原型模式的一個(gè)實(shí)現(xiàn)。

          HTMLFilter的clone方法實(shí)際就是在當(dāng)前HTMLFilter實(shí)例中再生成一個(gè)同樣的實(shí)例。這樣 在處理多個(gè)并發(fā)請(qǐng)求時(shí),不用通過同一個(gè)過濾器實(shí)例進(jìn)行處理,提高了性能。但是HTMLFilter的clone方法是采取new方法來實(shí)現(xiàn),不如直接使用 Object的native方法速度快。

          因?yàn)樵贒bFilterManager中是根據(jù)配置使用類反射機(jī)制動(dòng)態(tài)分別生成包括HTMLFilter在內(nèi)的過濾器實(shí)例。但是每種過濾器實(shí)例只有一個(gè),為了使得大量用戶不必爭奪一個(gè)過濾器實(shí)例來實(shí)現(xiàn)過濾,就采取了克隆方式,這種實(shí)戰(zhàn)手法可以借鑒在自己的應(yīng)用系統(tǒng)中。

          2.7  主題監(jiān)測與觀察者模式

          觀察者(Observer)模式是定義對(duì)象之間一對(duì)多的依賴關(guān)系,當(dāng)一個(gè)被觀察的對(duì)象發(fā)生改變時(shí),所有依賴于它的對(duì)象都會(huì)得到通知并采取相應(yīng)行為。

          使用觀察者模式的優(yōu)點(diǎn)是將被觀察者和觀察者解耦,從而可以不影響被觀察者繼續(xù)自己的行為動(dòng)作。觀察者模式適合應(yīng)用于一些“事件觸發(fā)”場合。

          在Jive中,用戶也許會(huì)對(duì)某個(gè)主題感興趣,希望關(guān)于此主題發(fā)生的任何新的討論能通過電子郵件通知他,因此他訂閱監(jiān)視了這個(gè)主題。因?yàn)檫@個(gè)功能的實(shí)現(xiàn)會(huì)引入電子郵件的發(fā)送。在前面章節(jié)已經(jīng)討論了電子郵件發(fā)送有可能因?yàn)?a target=_blank>網(wǎng)絡(luò)原因延遲,如果在有人回復(fù)這個(gè)主題時(shí),立即進(jìn)行電子郵件發(fā)送,通知所有訂閱該主題的用戶。那么該用戶可能等待很長時(shí)間得不到正常回應(yīng)。

          使用觀察者模式,可以通過觸發(fā)一個(gè)觀察者,由觀察者通過另外線程來實(shí)施郵件發(fā)送,而被觀察者發(fā)出觸發(fā)通知后,可以繼續(xù)自己原來的邏輯行為。

          看看Jive的WatchManager類:

          public interface WatchManager {

              //正常監(jiān)察類型,用戶在這個(gè)主題更新后再次訪問時(shí),會(huì)明顯地發(fā)現(xiàn)

              public static final int NORMAL_WATCH = 0;

               // 當(dāng)主題變化時(shí),通過電子郵件通知用戶

              public static final int EMAIL_NOTIFY_WATCH = 1;

              //設(shè)置一個(gè)主題被觀察的時(shí)間,默認(rèn)為30天

              public void setDeleteDays(int deleteDays) throws UnauthorizedException;

              public int getDeleteDays();

              //是否激活了E-mail提醒

              public boolean isEmailNotifyEnabled() throws UnauthorizedException;

              public void setEmailNotifyEnabled(boolean enabled) throws UnauthorizedException;

              //保存E-mail的內(nèi)容

              public String getEmailBody() throws UnauthorizedException;

              public void setEmailBody(String body) throws UnauthorizedException;

              //保存E-mail的主題

              public String getEmailSubject() throws UnauthorizedException;

          public void setEmailSubject(String subject) throws UnauthorizedException;

              …

           

              //為某個(gè)主題創(chuàng)建一個(gè)觀察者

              public void createWatch(User user, ForumThread thread, int watchType)

                      throws UnauthorizedException;

              //刪除某個(gè)主題的觀察者

              public void deleteWatch(User user, ForumThread thread, int watchType)

              //得到一個(gè)主題的所有觀察者

              public Iterator getWatchedForumThreads(User user, int watchType)

                      throws UnauthorizedException;

              //判斷一個(gè)用戶是否在觀察監(jiān)視該主題

              public boolean isWatchedThread(User user, ForumThread thread, int watchType)

                      throws UnauthorizedException;

              …

          }

          DbWatchManager是WatchManager的一個(gè)子類,通過數(shù)據(jù)庫保存著有關(guān)某個(gè)主題被哪些用戶監(jiān)視等數(shù)據(jù)資料。WatchManager對(duì)象是隨同DbForumFactory()一起生成的。

          在DbWatchManager中有一個(gè)WatchManager沒有的很重要的方法——通知方法:

          protected void notifyWatches(ForumThread thread) {

               //If watches are turned on.

              if (!emailNotifyEnabled) {

                      return;

               }

               //通知所有觀察這個(gè)主題的用戶

               EmailWatchUpdateTask task = new EmailWatchUpdateTask(this, factory, thread);

               TaskEngine.addTask(task);

           }

          這個(gè)方法用來觸發(fā)所有有關(guān)這個(gè)主題的監(jiān)視或訂閱用戶,以E-mail發(fā)送提醒他們。那么這個(gè)通知方法本身又是如何被觸發(fā)的?從功能上分析,應(yīng)該是在發(fā)表新帖子時(shí)觸發(fā)。

          在DbForumThread的addMessage的最后一行有一句:

          factory.watchManager.notifyWatches(this);

          這其實(shí)是調(diào)用了DbWatchManager的notifyWatches方法,因此確實(shí)是在增加新帖子時(shí)觸發(fā)了該帖子的所有觀察者。

          notifyWatches方法中在執(zhí)行E-mail通知用戶時(shí),使用了TaskEngine來執(zhí)行E- mail發(fā)送。E-mailWatchUpdateTask是一個(gè)線程類,而TaskEngine是線程任務(wù)管理器,專門按要求啟動(dòng)如E- mailWatchUpdateTask這樣的任務(wù)線程。其實(shí)TaskEngine是一個(gè)簡單的線程池,它不斷通過查詢Queue是否有可運(yùn)行的線程,如 果有就直接運(yùn)行線程。

          public class TaskEngine {

              //任務(wù)列表

              private static LinkedList taskList = null;

              //工作數(shù)組

              private static Thread[] workers = null;

              private static Timer taskTimer = null;

              private static Object lock = new Object();

           

              static {

                  //根據(jù)配置文件初始化任務(wù)啟動(dòng)時(shí)間

                  taskTimer = new Timer(true);

                  // 默認(rèn)使用7個(gè)線程來裝載啟動(dòng)任務(wù)

                  workers = new Thread[7];

                  taskList = new LinkedList();

                  for (int i=0; i<workers.length; i++) {

                       // TaskEngineWorker是個(gè)簡單的線程類

                      TaskEngineWorker worker = new TaskEngineWorker();

                      workers[i] = new Thread(worker);

                      workers[i].setDaemon(true);

                      workers[i].start();         //啟動(dòng)TaskEngineWorker這個(gè)線程

                  }

              }

              //TaskEngineWorker內(nèi)部類

              private static class TaskEngineWorker implements Runnable {

                  private boolean done = false;

                  public void run() {

                      while (!done) {

                          //運(yùn)行nextTask方法

                          nextTask().run();

                      }

                  }

              }

              // nextTask()返回的是一個(gè)可運(yùn)行線程,是任務(wù)列表Queue的一個(gè)讀取者

              private static Runnable nextTask() {

                  synchronized(lock) {

                      // 如果沒有任務(wù),就鎖定在這里

                      while (taskList.isEmpty()) {

                          try {

                              lock.wait();        //等待解鎖

                          } catch (InterruptedException ie) { }

                      }

                      //從任務(wù)列表中取出第一個(gè)任務(wù)線程

                      return (Runnable)taskList.removeLast();

                  }

              }

              public static void addTask(Runnable r) {

                  addTask(r, Thread.NORM_PRIORITY);

              }

              //這是任務(wù)列表Queue的生產(chǎn)者

              public static void addTask(Runnable task, int priority) {

                  synchronized(lock) {

                      taskList.addFirst(task);

                      //提醒所有鎖在lock這里的線程可以運(yùn)行了

                      //這是線程的互相通知機(jī)制,可參考線程參考資料

                      lock.notifyAll();

                  }

              }

              …

          }

          在TaskEngine中啟動(dòng)設(shè)置了一個(gè)消息管道Queue和兩個(gè)線程。一個(gè)線程是負(fù)責(zé)向Queue里放入 Object,可謂是消息的生產(chǎn)者;而另外一個(gè)線程負(fù)責(zé)從Queue中取出Object,如果Queue中沒有Object,那它就鎖定(Block)在 那里,直到Queue中有Object,因?yàn)檫@些Object本身也是線程,因此它取出后就直接運(yùn)行它們。

          這個(gè)TaskEngine建立的模型非常類似JMS(Java消息系統(tǒng)),雖然它們功能類似,但不同的是: JMS是一個(gè)分布式消息發(fā)布機(jī)制,可以在多臺(tái)服務(wù)器上運(yùn)行,處理能力要強(qiáng)大得多。而TaskEngine由于基于線程基礎(chǔ),因此不能跨JVM實(shí)現(xiàn)。可以說 TaskEngine是一個(gè)微觀組件,而JMS則是一個(gè)宏觀架構(gòu)系統(tǒng)。JMS相關(guān)討論將在后面章節(jié)進(jìn)行。

          以上討論了Jive系統(tǒng)中觀察者模式的實(shí)現(xiàn),Jive使用線程比較基礎(chǔ)的概念實(shí)現(xiàn)了觀察者模式,當(dāng)然有助于了解J2EE很多底層的基礎(chǔ)知識(shí),整個(gè)Web容器的技術(shù)實(shí)現(xiàn)就是基于線程池原理建立的。

          Java的JDK則提供了比較方便的觀察者模式API——java.util.Observable和java.util.Observer,它們的用戶非常簡單,只要被觀察者繼承Observable,然后使用下列語句設(shè)置觀察點(diǎn):

          setChanged();

          notifyObservers(name); //一旦執(zhí)行本代碼,就觸發(fā)觀察者了

          而觀察者只要實(shí)現(xiàn)Observer接口,并實(shí)現(xiàn)update方法,在update方法中將被觀察者觸發(fā)后傳來的object進(jìn)行處理。舉例如下:

          網(wǎng)上商店中商品價(jià)格可能發(fā)生變化,如果需要在價(jià)格變化時(shí),首頁能夠自動(dòng)顯示這些降價(jià)產(chǎn)品,那么使用觀察者模式將方便得多。首先,商品是一個(gè)被觀察者:

          public class product extends Observable{

            private float price;

            public float getPrice(){ return price;}

            public void setPrice(){

             this.price=price;

           //商品價(jià)格發(fā)生變化,觸發(fā)觀察者

             setChanged();

             notifyObservers(new Float(price));

            }

            ...

          }

          價(jià)格觀察者實(shí)現(xiàn)observer接口:

          public class PriceObserver implements Observer{

            private float price=0;

            public void update(Observable obj,Object arg){

              if (arg instanceof Float){

               price=((Float)arg).floatValue();

               System.out.println("PriceObserver :price changet to "+price);

              }

            }

          }

          這樣,一個(gè)簡單的觀察者模式就很容易地實(shí)現(xiàn)了。

          posted @ 2007-05-18 18:46 edsonjava 閱讀(718) | 評(píng)論 (0)編輯 收藏
           

          前言

          Jive是一個(gè)開放的Java源代碼項(xiàng)目。其目標(biāo)是建設(shè)一個(gè)開放結(jié)構(gòu)的,強(qiáng)壯的,易于擴(kuò)展的基于JSP的論壇。在其設(shè)計(jì)目標(biāo)的指導(dǎo)下,其結(jié)構(gòu)設(shè)計(jì)得非常得好,融合了很多新的觀念,比如Design Pattern,可更換的Skin,可插入Plug等等。詳細(xì)解讀其源代碼對(duì)于理解這些新的設(shè)計(jì)上的概念是很有裨益的。如果你對(duì)Design Pattern和Java語言有一定的了解,但是還是會(huì)時(shí)常迷惑于其中的話,不妨研究研究Jive源代碼,一定會(huì)對(duì)其中的很多概念有更深入的理解。這篇文章源于我的Jive源代碼研究筆記,希望能夠提綱挈領(lǐng),帶領(lǐng)大家進(jìn)入到這個(gè)美好的世界。當(dāng)然,如果沒有時(shí)間仔細(xì)地看源代碼的話,看看這篇文章,我想也是會(huì)有一些幫助的。

          再開始之前,需要指出的是,Jive中對(duì)Design Pattern的應(yīng)用,并沒有拘禮與GOF書中所給出的實(shí)現(xiàn)方法,而是有許多變通的地方。一方面,我想是由于具體的實(shí)際需要,另一方面,我想這也是設(shè)計(jì)觀念進(jìn)化的結(jié)果吧。因而,這些變通的地方,將是我講解的重點(diǎn)。





          回頁首


          整體結(jié)構(gòu)概敘

          基于一個(gè)OO的設(shè)計(jì)原則:面向接口編程,而不是針對(duì)實(shí)現(xiàn)編程。Jive在設(shè)計(jì)的時(shí)候,把其大部分的基本對(duì)象都設(shè)計(jì)為接口或者抽象類。在Jive中,基本的接口有Forum,F(xiàn)orumMessage,F(xiàn)orumThread,Group,User,Authorization和Query。我們可以很容易的從這些接口的名字來知道他們的功用,下面的類圖給出了這些類之間的一些靜態(tài)關(guān)系:



          圖1:Jive整體關(guān)系
          圖1:Jive整體關(guān)系

          你可能會(huì)有疑問,為什么會(huì)都是接口呢?這是基于擴(kuò)展性考慮的。在Jive給出的實(shí)現(xiàn)中,所有的這些接口,F(xiàn)orum,F(xiàn)orumMessage,User等等,都使用數(shù)據(jù)庫來實(shí)現(xiàn)的,一條消息,或者一個(gè)用戶對(duì)應(yīng)于數(shù)據(jù)庫中的一條消息Jive使用了DbForum,DbForumMessage,DbUser等類來實(shí)現(xiàn)這些接口,通過JDBC來操作數(shù)據(jù)庫,使之作為論壇的底層支撐。

          然而,有時(shí)候,或許我們并不想使用數(shù)據(jù)庫,比如我們想只是使用文件系統(tǒng)來作為論壇的底層支撐,這時(shí)候,我們需要做的只是編碼實(shí)現(xiàn)了Forum等等接口的諸如FileFroum,F(xiàn)ileForumMessage等對(duì)象,然后嵌入Jive中即可,原有的任何代碼都可以不用改變!!!這就是面向接口編程的威力了!

          下面來看看具體的設(shè)計(jì)和編碼。






          AbstractFactory模式和可擴(kuò)展性

          如果要實(shí)現(xiàn)較好的可擴(kuò)展性,AbstractFactory模式確實(shí)是一件利器。如上面所說,如果要?jiǎng)?chuàng)建的Forum接口的不同實(shí)現(xiàn),而又不想更改代碼的話,就需要用到抽象工廠了。再Jive中,AuthorizationFactory類是一個(gè)抽象類,用來創(chuàng)建Authorization對(duì)象。這是一個(gè)抽象工廠,可以通過不同的子類來創(chuàng)建不同的Authorization對(duì)象。這個(gè)工廠的實(shí)現(xiàn)方法是:

          在AuthorizationFactory中使用一個(gè)private static變量factory,用來引用具體的抽象工廠的實(shí)例:
          private static AuthorizationFactory factory = null;

          用一個(gè)private static的String,來指明具體的抽象工廠的子類類名:
          private static String className ="com.coolservlets.forum.database.DbAuthorizationFactory";

          然后是用一個(gè)private static的loadAuthorizationFactory方法來給這個(gè)factory變量賦值,生成具體的抽象工廠類:

             private static void loadAuthorizationFactory() {
                      if (factory == null) {
                      synchronized(className) {
                      if (factory == null) {
                      String classNameProp = PropertyManager.getProperty(
                      "AuthorizationFactory.className"
                      );
                      if (classNameProp != null) {
                      className = classNameProp;
                      }
                      try {
                      Class c = Class.forName(className);
                      factory = (AuthorizationFactory)c.newInstance();
                      }
                      catch (Exception e) {
                      System.err.println("Exception loading class: " + e);
                      e.printStackTrace();
                      }
                      }
                      }
                      }
                      }

          在static的getAuthorization方法返回一個(gè)Authorization的過程中,先初始化工廠類factory變量,然后用factory的createAuthorization方法來創(chuàng)建:

             public static Authorization getAuthorization(String username,
                      String password) throws UnauthorizedException
                      {
                      loadAuthorizationFactory();
                      return factory.createAuthorization(username, password);
                      }

          不同的子類有不同的createAuthorization方法的實(shí)現(xiàn)。比如在DbAuthorizationFactory這個(gè)AuthorizationFactory的數(shù)據(jù)庫實(shí)現(xiàn)子類中,createAuthorization方法是這樣實(shí)現(xiàn)的:

             public Authorization createAuthorization(String username, String password)
                      throws UnauthorizedException
                      {
                      if (username == null || password == null) {
                      throw new UnauthorizedException();
                      }
                      password = StringUtils.hash(password);
                      int userID = 0;
                      Connection con = null;
                      PreparedStatement pstmt = null;
                      try {
                      con = DbConnectionManager.getConnection();
                      pstmt = con.prepareStatement(AUTHORIZE);
                      pstmt.setString(1, username);
                      pstmt.setString(2, password);
                      ResultSet rs = pstmt.executeQuery();
                      if (!rs.next()) {
                      throw new UnauthorizedException();
                      }
                      userID = rs.getInt(1);
                      }
                      catch( SQLException sqle ) {
                      System.err.println("Exception in DbAuthorizationFactory:" + sqle);
                      sqle.printStackTrace();
                      throw new UnauthorizedException();
                      }
                      finally {
                      try {  pstmt.close(); }
                      catch (Exception e) { e.printStackTrace(); }
                      try {  con.close();   }
                      catch (Exception e) { e.printStackTrace(); }
                      }
                      return new DbAuthorization(userID);
                      }

          在這個(gè)類中,可以看到抽象類和具體的子類之間的關(guān)系,它們是如何協(xié)作的,又是如何劃分抽象方法和非抽象方法的,這都是值得注意的地方。一般的,抽象方法需要子類來實(shí)現(xiàn),而抽象類中的非抽象方法應(yīng)該所有子類所能夠共享的,或者可是說,是定義在抽象方法之上的較高層的方法。這確實(shí)是一個(gè)抽象工廠的好例子!雖然實(shí)現(xiàn)的方法已經(jīng)和GOF中給出的實(shí)現(xiàn)相差較遠(yuǎn)了,但思想沒變,這兒的實(shí)現(xiàn),也確實(shí)是要巧妙的些。

          還有就是靜態(tài)方法的使用,使得這個(gè)類看起來有些Singleton的意味。這使得對(duì)于AbstractFactory的創(chuàng)建變得簡單。

          下面的類圖給出了這個(gè)AbstractFactory的實(shí)現(xiàn)的總體情況:



          圖2:AbstractFactory模式的實(shí)現(xiàn)類圖
          圖2:AbstractFactory模式的實(shí)現(xiàn)類圖

          在AuthorizationFactory中定義的其它方法,涉及到具體的如何創(chuàng)建Authorization,都是作為abstract方法出現(xiàn),具體實(shí)現(xiàn)留給子類來完成。

          這樣,在需要生成一個(gè)Authorization的時(shí)候,只需要調(diào)用AuthorizationFactory的靜態(tài)方法getAuthorization就可以了,由子類實(shí)現(xiàn)了具體的細(xì)節(jié)。

          其它的,如同上面講到的,在創(chuàng)建Forum的時(shí)候用的ForumFactory,具有同上面一樣的實(shí)現(xiàn),這就是模式之所以稱為模式的所在了。





          回頁首


          Proxy模式和權(quán)限控制

          Proxy模式的功能有很多,比如遠(yuǎn)程代理,用來給遠(yuǎn)程對(duì)象提供一個(gè)本地代表;虛代理,用來為創(chuàng)建開大開銷的對(duì)象提供緩沖,等等。在Jive中使用的是保護(hù)代理,為被保護(hù)的對(duì)象提供權(quán)限控制。

          我們都知道在一個(gè)論壇中,權(quán)限的控制是必須的,否則論壇就很可能會(huì)被搞得一團(tuán)糟。Jive中引入Proxy對(duì)象,Authorization接口以及權(quán)限描敘屬類來提供對(duì)論壇的保護(hù)。

          以ForumFactory為例,一個(gè)額外的ForumFactoryProxy來處理權(quán)限認(rèn)證的工作,它為某一個(gè)ForumFactory提供了一個(gè)代理,保證只有授權(quán)的用戶才能夠存取ForumFactory的某些操作。實(shí)際上ForumFactory在這兒不僅僅只是一個(gè)生成Forum的類的,它更像是一個(gè)Forum的管理類。提供了添加,刪除,枚舉等等一系列的功能,而有些功能不是什么樣的人都可以使用的,因而引入了另外的一個(gè)代理類來處理權(quán)限的問題。

          當(dāng)然,代理類需要繼承ForumFactory,以使方法簽名一致: ForumFactoryProxy extends ForumFactory

          在它的構(gòu)造方法中,就提供了一個(gè)ForumFactory對(duì)象,這是需要被代理的對(duì)象;一個(gè)Authorization對(duì)象,提供用戶信息;還有一個(gè)ForumPermissions,提供認(rèn)證信息:

             public ForumFactoryProxy(ForumFactory factory, Authorization authorization,
                      ForumPermissions permissions)
                      {
                      this.factory = factory;
                      this.authorization = authorization;
                      this.permissions = permissions;
                      }

          一般的代理過程都是這樣的,在訪問某個(gè)方法之前,必須接受權(quán)限的檢查,以createForum為例:

             public Forum createForum(String name, String description)
                      throws UnauthorizedException, ForumAlreadyExistsException
                      {
                      if (permissions.get(ForumPermissions.SYSTEM_ADMIN)) {
                      Forum newForum = factory.createForum(name, description);
                      return new ForumProxy(newForum, authorization, permissions);
                      }
                      else {
                      throw new UnauthorizedException();
                      }
                      }

          下面給出這個(gè)模式的類圖:



          圖3:Proxy模式的類圖
          圖3:Proxy模式的類圖

          這個(gè)模式的實(shí)現(xiàn)基本上和GOF中所給出的實(shí)現(xiàn)一致。在Jive中,幾乎所有的接口,F(xiàn)orum,F(xiàn)orumMessage,F(xiàn)orumThread等等,都會(huì)有一個(gè)相應(yīng)的Proxy對(duì)象來進(jìn)行權(quán)限控制。而在創(chuàng)建具體的對(duì)象的時(shí)候,都是用相應(yīng)的Proxy對(duì)象來代替原有的對(duì)象返回的。例如在ForumFactory的getInstance()方法中需要返回一個(gè)Forum的時(shí)候,Jive是這樣做的:

          public static ForumFactory getInstance(Authorization authorization) {
                      ......
                      ForumFactoryProxy proxy = new ForumFactoryProxy(factory,authorization, factory.getPermissions(authorization));
                      return proxy;
                      }

          因而,所有被創(chuàng)建的對(duì)象實(shí)際上都是Proxy對(duì)象,抽象工廠保證了沒有權(quán)限驗(yàn)證的對(duì)象根本不會(huì)客戶所得到,它們只會(huì)在Proxy的內(nèi)部扮演角色,而永遠(yuǎn)不會(huì)被外部對(duì)象所存取,這樣,就從根本上保證了論壇的安全。





          回頁首


          Decorator模式和過濾器

          一般的在OO設(shè)計(jì)中,而外功能的添加是通過繼承來實(shí)現(xiàn)的,但是繼承有的時(shí)候不夠靈活,而且當(dāng)功能的組合很多的時(shí)候,繼承的子類就會(huì)成幾何級(jí)數(shù)增長,使得類多的難以控制。正是基于這樣的考慮,Decorator模式得以誕生。

          Decorator模式相當(dāng)于封裝了某個(gè)特定的操作,當(dāng)某個(gè)對(duì)象需要這個(gè)操作的時(shí)候,加上這個(gè)Decorator即可。并且,多個(gè)Decorator還可以組合,以提供更多的功能。

          在Jive中,Decorator模式應(yīng)用在一些過濾器(Filter)中。Filter提供對(duì)ForumMessage對(duì)象內(nèi)容的重新構(gòu)造。比如,當(dāng)一個(gè)ForumMessage對(duì)象流過一個(gè)名為FilterCodeHighlight的過濾器后,存在于消息中的所有Java源代碼文本,會(huì)被重新構(gòu)造為具有語法高亮顯示的消息。在比如,當(dāng)經(jīng)過了語法高亮修飾的消息再流過一個(gè)名為FilterHtml的過濾器后,消息中的HTML片斷會(huì)被注釋可以在HTML內(nèi)部顯示文本,這樣就防止了用戶輸入了HTML控制標(biāo)簽后,使得頁面顯示不正常的問題。

          Jive中,所有的過濾器繼承于一個(gè)抽象類ForumMessageFilter,而ForumMessageFilter又實(shí)現(xiàn)了ForumMessage接口。也就是說,每一個(gè)過濾器實(shí)際上也是一個(gè)ForumMessage對(duì)象。

          ForumMessageFilter中還封裝一個(gè)ForumMessage對(duì)象。進(jìn)行過濾的方法很簡單,使用的是getBody(),比如在FilterCodeHighlight這個(gè)類中:

             public String getBody() {
                      return highlightCode(message.getBody());
                      }

          highlightCode是一個(gè)private方法,實(shí)施具體的過濾的細(xì)節(jié)。getBody()方法實(shí)際上是定義在ForumMessage接口中的,當(dāng)調(diào)用過濾器的getBody()方法時(shí),就能夠得到結(jié)構(gòu)重整后的ForumMessage對(duì)象了。這個(gè)對(duì)象可以被其他客戶引用,也可以在傳遞給另外的過濾器,實(shí)施進(jìn)一步的操作。

          在實(shí)現(xiàn)一個(gè)具體的消息的過濾的時(shí)候,在Forum中有addForumMessageFilter(),applyFilters()方法,用來實(shí)現(xiàn)對(duì)過濾器的應(yīng)用。

          對(duì)一個(gè)Forum,使用addForumMessageFilter()方法添加一個(gè)Filter的時(shí)候,并沒有指定一個(gè)具體的Message,而只是一個(gè)規(guī)則(Filter中封裝了過濾規(guī)則),然后applyFilter()方法中,實(shí)施這些規(guī)則:

             public ForumMessage applyFilters(ForumMessage message) {
                      //Loop through filters and apply them
                      for (int i=0; i < filters.length; i++) {
                      message = filters[i].clone(message);
                      }
                      return message;
                      }

          過濾器的clone()方法,為過濾器復(fù)制消息體。這個(gè)方法的使用,分離了在過濾器中對(duì)于消息體和過濾規(guī)則的初始化過程,這也是一個(gè)值得借鑒的技巧!

          下面給出Decorator模式的類圖:



          圖4:Decorator模式的類圖
          圖4:Decorator模式的類圖

          我們可以看到Decorator模式實(shí)際上和Proxy模式是很相近的,但是它們代表兩個(gè)不同的功能含義。Proxy模式提供一個(gè)對(duì)象的控制,而Decorator模式則是為對(duì)象提供額外的功能。





          回頁首


          Iterator模式和論壇的瀏覽

          Iterator模式用來分離數(shù)據(jù)結(jié)構(gòu)和遍歷算法,降低兩者之間的耦合度,以使得同一個(gè)數(shù)據(jù)結(jié)構(gòu)用不同的算法遍歷時(shí),仍能夠具有相同的接口,另一方面,Iterator模式使得當(dāng)改換遍歷算法后,不需要更改程序的代碼。

          在Java的JDK中本身就定義有一個(gè)Iterator接口,在Iterator接口中僅僅定義了三個(gè)方法,hasNext()判斷是否遍歷完最后一個(gè)元素,next()方法返回要遍歷的數(shù)據(jù)結(jié)構(gòu)中一個(gè)對(duì)象,remove()則刪除當(dāng)前對(duì)象。Jive中使用IteratorProxy抽象類繼承了這一接口。這兒Proxy的含義和上面一樣,也就是說,這個(gè)IteratorProxy出了會(huì)實(shí)現(xiàn)Iterator的遍歷功能外,還會(huì)有代理權(quán)限控制的功能。

          對(duì)于論壇中的基本對(duì)象Forum,F(xiàn)orumThread,F(xiàn)orumMessage,Group,User都有相應(yīng)的遍歷器。比如對(duì)應(yīng)于Forum接口有ForumIteratorProxy對(duì)象。這個(gè)ForumIteratorProxy遍歷器就相當(dāng)于一個(gè)封裝了一系列Forum對(duì)象的集合類,通過定義好的接口hasNext()和next()可以方便的遍歷這個(gè)集合,而并不需要知道是如何遍歷這個(gè)集合的。遍歷的算法可能很簡單,也可能很復(fù)雜,但是對(duì)于外部的客戶而言,這并沒有任何的區(qū)別。

          而對(duì)于論壇中具體的遍歷方法,這取決于具體的實(shí)現(xiàn),在Jive中給出的是數(shù)據(jù)庫的實(shí)現(xiàn)。

          我們就以MessageIteratorProxy為例,來講解Iterator模式的用法。

          DbThreadIterator對(duì)象實(shí)現(xiàn)了Iterator接口,是對(duì)于一個(gè)Thread中所有Message的遍歷器,我們來看看它是如何實(shí)現(xiàn)的。

          hasNext()判斷在這個(gè)Thread中是不是還有下一條Message:

          public boolean hasNext() {
                      if (currentIndex+1 >= messages.length) {
                      return false;
                      }
                      return true;
                      }

          next()方法從數(shù)據(jù)庫中取出與在這個(gè)Thread中的下一條Message:

             public Object next() throws java.util.NoSuchElementException {
                      ForumMessage message = null;
                      if (nextMessage != null) {
                      message = nextMessage;
                      nextMessage = null;
                      }
                      else {
                      message = getNextMessage();
                      if (message == null) {
                      throw new java.util.NoSuchElementException();
                      }
                      }
                      return message;
                      }

          這樣,通過對(duì)數(shù)據(jù)庫的操作,DbThreadIterator實(shí)現(xiàn)了對(duì)一個(gè)Thread中所有Message遍歷的方法。

          再ForumThread接口中有messages()方法,返回在這個(gè)Thread中的所有Message的一個(gè)遍歷器(Iterator),實(shí)際上也就是返回了一個(gè)Message的集合:
          public Iterator messages();

          在DbForumThread中實(shí)現(xiàn)了這個(gè)方法:
          public Iterator messages() {return new DbThreadIterator(this);}

          從DbForumThread的messages()方法中所返回的就是這個(gè)Thread中所有Message的一個(gè)遍歷器,通過這個(gè)遍歷器,我們就可以訪問Thread中的所有的Message了。當(dāng)然,事情還沒有完,由于權(quán)限的問題,我們還需要構(gòu)造這個(gè)遍歷器的Proxy對(duì)象,然后通過這個(gè)Proxy對(duì)象來訪問遍歷器。

          下面的類圖給出了在Jive中Iterator模式的實(shí)現(xiàn)方法:



          圖5:Jive中Iterator模式的實(shí)現(xiàn)
          圖5:Jive中Iterator模式的實(shí)現(xiàn)

          在Jive中,因?yàn)樵谝粋€(gè)Thread之下,Message是按樹形結(jié)構(gòu)組織的,因而,當(dāng)需要層級(jí)表示一個(gè)Thread中的Message之間的關(guān)系的時(shí)候,僅僅用上面講到的線性的Iterator是不夠的。這時(shí)候,對(duì)Iterator的概念進(jìn)行推廣,就引入了TreeWalker接口。

          顧名思義,TreeWalker提供了遍歷一個(gè)樹和存取樹上節(jié)點(diǎn)的方法:

          public interface TreeWalker {
                      public ForumMessage getRoot();
                      public ForumMessage getChild(ForumMessage parent, int index);
                      public int getChildCount(ForumMessage parent);
                      public int getRecursiveChildCount(ForumMessage parent);
                      public int getIndexOfChild(ForumMessage parent, ForumMessage child);
                      public boolean isLeaf(ForumMessage node);

          TreeWalker只是Iterator的簡單推廣,并沒有Iterator應(yīng)用的那么廣泛,而且,也可以很容易的在TreeWalker上面在套一層Iterator的借口,讓它在某些情況下行使Iterator的職責(zé)。這兒就不再多討論了。

          再此,Jive設(shè)計(jì)中所有涉及到的設(shè)計(jì)模式的地方,基本上都講完了,看完了之后,是不是對(duì)設(shè)計(jì)模式有了更進(jìn)一步的了解了呢?

          下一部分的內(nèi)容,將會(huì)涉及到具體的編碼,深入到JSP的內(nèi)部,我們將會(huì)看到Jive中是如何實(shí)現(xiàn)可更換的Skin的,還會(huì)涉及Tag Library的一些內(nèi)容。好了,這次就到這兒了。下次再見。

          posted @ 2007-05-18 15:09 edsonjava 閱讀(254) | 評(píng)論 (0)編輯 收藏
           
          現(xiàn)在越來越多的項(xiàng)目是基于Linux或Unix下的,而在Linux上給客戶上安裝一個(gè)項(xiàng)目,需要進(jìn)行許多的安裝設(shè)置過程,比如數(shù)據(jù)庫的,WebLogic Server的。現(xiàn)寫下基于Red hat Linux7.1 +jdk1.3+WebLogic Server7.0 +oracle9.2 的安裝配置過程。

          一.安裝好linux ,安裝過程比較簡單,不在此敘述.

          二.JDK的安裝配置.
          一般以root用戶安裝。
          先從SUN網(wǎng)站上下載一個(gè)jdk.比如:j2sdk-1_3_1_06-linux-i586.bin,放到/usr/local
          下,
            chmod a+x j2sdk-1_3_0-linux.bin(添加執(zhí)行權(quán)限)
            ./j2sdk-1_3_0-linux.bin

          安裝RPM文件格式:
            chmod a+x j2sdk-1_3_0-linux-rpm.bin
            ./j2sdk-1_3_0-linux-rpm.bin
            rpm -iv j2sdk-1_3_0-linux.rpm
            rpm -iv --force j2sdk-1_3_0-linux.rpm
            ./j2sdk-1_3_1_06-linux-i586.bin

          設(shè)置環(huán)境變量:
            # vi /etc/profile
          里面添加:
            export JAVA_HOME=/usr/local/jdk1.3.1_06/
            export CLASSPATH=.:/usr/local/ jdk1.3.1_06/lib
          PATH=$PATH:$JAVA_HOME/bin

          三、Oracle 9i的安裝配置
          1.從oracle網(wǎng)站下載oracle9i.
          2.解壓oracle文件
          gunzip Linux9i_Disk1.cpio.gz Linux9i_Disk2.cpio.gz Linux9i_Disk3.cpio.gz

          cpio -idmv < Linux9i_Disk1.cpio
          cpio -idmv < Linux9i_Disk2.cpio
          cpio -idmv < Linux9i_Disk3.cpio

          3.以root用戶登陸,創(chuàng)建oracle用戶,目錄,設(shè)置oracle環(huán)境變量.
            Create Oracle User Accounts
            # groupadd dba
            # groupadd oinstall
            # useradd -g oinstall -G dba oracle
            # passwd ********
            Create Oracle Directories
            # mkdir /opt/oracle
            # mkdir /opt/oracle/product
            # mkdir /opt/oracle/product/9.2.0
            # chown -R oracle.oinstall /opt/oracle
            # mkdir /var/opt/oracle
            # chown oracle.dba /var/opt/oracle
            # chmod 755 /var/opt/oracle
            Set Oracle Environments
            As the oracle user execute the following commands:
            # vi /home/oracle/.bash_profile添加

            export ORACLE_BASE=/usr/local/oracle
            export ORACLE_HOME=$ORACLE_BASE/product/9.2.0
            export ORACLE_SID=orcl
            export ORACLE_TERM=xterm
            export ORA_NLS33=$ORACLE_HOME/ocommon/nls/admin/data
            LD_LIBRARY_PATH=$ORACLE_HOME/lib:/lib:/usr/lib
            LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib
            export LD_LIBRARY_PATH
            CLASSPATH=$ORACLE_HOME/JRE:$ORACLE_HOME/jlib:$ORACLE_HOME/rdbms/jlib
            CLASSPATH=$CLASSPATH:$ORACLE_HOME/network/jlib
            export CLASSPATH
            PATH=$PATH:$HOME/bin:$ORACLE_HOME/bin

          調(diào)整內(nèi)存
            Shared Memory
            su root
            # vi/etc/sysctl.conf里添加
            kernel.shmmax=1073741824
          4.安裝oracle
            進(jìn)入Disk1目錄
            cd Disk1
            在控制臺(tái)窗口敲入
            ./runInstaller
          安裝完了以后,啟動(dòng)數(shù)據(jù)庫
          oracle$ sqlplus /nolog
          SQL> connect / as sysdba
          SQL> startup

          oracle 的安裝過程比較復(fù)雜,而且如果你的開發(fā)包安裝的不夠全的話,會(huì)出現(xiàn)一些錯(cuò)誤,具體請(qǐng)參考: http://www.puschitz.com/InstallingOracle9i.shtml

          5.設(shè)置oracle 自啟動(dòng)

          (1)vi /etc/oratab
            orcl:/usr/local/oracle/product/9.2.0:Y
          (2)vi /home/oracle/.bash_profile
            ORACLE_SID=orcl
            ORAENV_ASK=NO
            export ORACLE_SID ORAENV_ASK
            . oraenv
          (3)在 /etc/rc.d/init.d 下創(chuàng)建一個(gè)dbora文件。
          內(nèi)容如下:

            #!/bin/sh
            ORA_HOME=/usr/local/oracle/product/9.2.0
            ORA_OWNER=oracle
            if [ ! -f $ORA_HOME/bin/dbstart ]
            then
            echo "Oracle startup: cannot start"
            exit
            fi
            case "$1" in
            'start') #start oracle database and listeners
            su - $ORA_OWNER -c "$ORA_HOME/bin/dbstart"
            su - $ORA_OWNER -c "$ORA_HOME/bin/lsnrctl start"
            ;;
            'stop') #stop oracle databse and listeners
            su - $ORA_OWNER -c "$ORA_HOME/bin/lsnrctl stop"
            su - $ORA_OWNER -c "$ORA_HOME/bin/dbshut"
            ;;
            esac

          把這個(gè)文件與下列文件聯(lián)接:
          ln -s /etc/rc.d/init.d/dbora /etc/rc.d/rc3.d/S99dbora
          ln -s /etc/rc.d/init.d/dbora /etc/rc.d/rc5.d/S99dbora
          ln -s /etc/rc.d/init.d/dbora /etc/rc.d/rc0.d/K10dbora

          (4)編輯dbstart文件
          以oracle用戶登陸
          vi /usr/local/oracle/product/9.2.0/bin/dbstart
          - add the following line:
          SPFILE=${ORACLE_HOME}/dbs/spfile${ORACLE_SID}.ora
          after this line: PFILE=${ORACLE_HOME}/dbs/init${ORACLE_SID}.ora

          - change:
          if [ -f $PFILE ] ; then
          to:
          if [ -f $PFILE -o -f $SPFILE ] ; then
          重啟動(dòng)服務(wù)器,檢查數(shù)據(jù)庫是否已經(jīng)起來。

          四、WebLogic 配置。
          從bea網(wǎng)站上下載一個(gè)WebLogic Server.開始安裝。
          [test@linux2 download]$ chmod a+x server701_linux.bin
          [test@linux2 download]$ ./server701_linux.bin
          按照提示安裝即可。

          五.設(shè)置WebLogic Server 自啟動(dòng).
          以root用戶登陸。
          vi /etc/rc.d/rc.local
          把su - test -c "/home/test/bea/user_projects/mydomain/startWebLogic.sh 2>&1> /dev/null &"
          (這里的 test是你安裝WebLogic Server時(shí)候的用戶名.)
          放到/etc/rc.d/rc.local里就行了,不過這樣有一個(gè)缺點(diǎn),你WebLogic Server啟動(dòng)后一直在后臺(tái)運(yùn)行,你不能看到上面的提示信息和出錯(cuò)信息.

          六.調(diào)整你的WebLogic Server,便于用戶的使用。
          設(shè)置默認(rèn) Web Application,從
            mydomain> Servers> myserver>connection>http>Default Web Application
          選中你的web application即可。
          設(shè)置你的首頁,在你web application里面的web.xml里面添加一句
            
            你得頁面
            


          以上安裝過程在Red hat Linux7.1 +jdk1.3+WebLogic Server7.0 +oracle9.2下安裝測試通過。
          posted @ 2007-05-17 18:21 edsonjava 閱讀(313) | 評(píng)論 (0)編輯 收藏
           

          1.安裝JDK
          首先,到http://java.sun.com/j2se/1.5.0/download.jsp
          下載最新版本的Linux 平臺(tái)的JDK,建議下載RPM自解壓格式的例如本文所用jdk-1_5_0_06-linux-i586-rpm.bin,先下載文件到/tmp,打開終端,輸入:
          cd /tmp
          su
          輸入root密碼
          直接執(zhí)行文件:
          ./jdk-1_5_0_06-linux-i586-rpm.bin
          然后會(huì)出現(xiàn)sun的協(xié)議(Sun Microsystems, Inc. Binary Code License Agreement),如果運(yùn)行jdk-1_5_0_06-linux-i586-rpm.bin無效請(qǐng)給予其相應(yīng)的運(yùn)行權(quán)限。
          想查看完整協(xié)議,不斷點(diǎn)擊more就可以了。如果看完了,或者像我一樣看不懂^__^就直接按q吧。
          出現(xiàn)提示:Do you agree to the above license terms? [yes or no]
          如果同意協(xié)議,請(qǐng)輸入yes
          然后自動(dòng)解壓出jdk-1_5_0_06-linux-i586.rpm,并且自動(dòng)安裝,如果不是root用戶,可能會(huì)出現(xiàn)類似
          error: cannot get exclusive lock on /var/lib/rpm/Packages
          error: cannot open Packages index using db3 -
          不允許的操作 (1)
          error: cannot open Packages database in /var/lib/rpm
          的錯(cuò)誤。
          最后顯示Done.安裝結(jié)束

          2.配置環(huán)境變量
          就像在windows下一樣,裝好JDK需要配置環(huán)境變量,否則系統(tǒng)找不到相應(yīng)的程序。先查看當(dāng)前系統(tǒng)環(huán)境變量中jdk的路徑:
          echo $JAVA_HOME
          如果安裝SuSE Linux時(shí)選擇了相應(yīng)的java的包,則顯示/usr/lib/jvm/java。再看當(dāng)前JDK版本:
          java -version
          我的機(jī)器上顯示如下信息:
          java version "1.4.2_06"
          Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.2_06-b03)
          Java HotSpot(TM) Client VM (build 1.4.2_06-b03, mixed mode)
          1.4.2_06版本,不是我們剛才安裝的版本(因?yàn)闆]有修改環(huán)境變量嘛)。
          我們剛才安裝的版本默認(rèn)在/usr/java/jdk1.5.0_06,我們把它加到環(huán)境變量。
          最簡單的辦法就是編輯/ect/profile,在文件最后的
          #
          # End of /etc/profile
          #
          上面添加如下文本:
          export JAVA_HOME=/usr/java/jdk1.5.0_06
          export CLASSPATH=.:$JAVA_HOME/jre/lib:$JAVA_HOME/lib/tools.jar
          export JRE_HOME=$JAVA_HOME/jre
          export PATH=$JAVA_HOME/bin:$PATH
          作用分別是設(shè)置 JAVA_HOME , CLASSPATH , JRE_HOME , PATH 所指向的路徑。跟windows下的意義一樣。如果不懂可以查閱相關(guān)文檔或者直接把以上文本復(fù)制粘貼到你的/ect/profile 中即可。
          注銷一下,使更改生效。
          再查看一下當(dāng)前的環(huán)境變量:
          echo $JAVA_HOME
          輸出:
          /usr/java/jdk1.5.0_06
          可以看到我們剛才裝的JDK生效了。
          然后輸入:
          java -version
          查看當(dāng)前JDK版本。
          輸出:
          java version "1.5.0_06"
          Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_06-b05)
          Java HotSpot(TM) Client VM (build 1.5.0_06-b05, mixed mode, sharing)
          說明我們環(huán)境變量配置成功了。

          posted @ 2007-05-17 16:16 edsonjava 閱讀(5947) | 評(píng)論 (2)編輯 收藏
           

          1 環(huán)境的搭建

          要使用JAVA開發(fā)Web應(yīng)用,必需要JAVA的運(yùn)行環(huán)境,還有開發(fā)環(huán)境。當(dāng)然Web開發(fā)少不了數(shù)據(jù)庫。Web程序要運(yùn)行也少不了Web服務(wù)器。

          這里我們選用JAVA運(yùn)行環(huán)境:J2SDk1.4

          數(shù)據(jù)庫:Mysql 4.0.15

          Web服務(wù)器:Tomcat 4.1.18

          1.1 JAVA的運(yùn)行環(huán)境

          首先要搭建的是JAVA的運(yùn)行環(huán)境。到Sun公司http://java.sun.com/j2se/1.4.2/download.html免費(fèi)下載j2sdk-1_4_2_05-linux-i586.bin然后拷貝到安裝目錄

          執(zhí)行二進(jìn)制文件即可解壓縮文件:

          [root@localhost jdk]#./ j2sdk-1_4_2_05-linux-i586.bin

          解壓縮完成之后就可以在當(dāng)前目錄下面找到j2sdk1.4.2_05目錄

          為了方便可以做一個(gè)簡單的連接到這個(gè)目錄

          [root@localhost jdk]#ln –s j2sdk1.4.2_05 jdk

          然后加入環(huán)境變量

          export JVAV_HOME=/var/jdk/jdk1.4

          export CLASSPATH=$JAVA_HOME/lib:$JAVA_HOME/jre/lib:.

          export PATH=$PATH:$JAVA_HOME/bin:$JAVA_HOME/jre/bin

          現(xiàn)在JAVA運(yùn)行環(huán)境就搭建好了,你可以試試寫一個(gè)java程序來驗(yàn)證

          [root@localhost jdk]#vi HelloWorld.java

          輸入如下內(nèi)容

          public class HelloWorld{

             public static void main(String args[]){

              System.out.println("HelloWrold");

             }

          }

          :wq

          寫盤并退出編輯器

          [root@localhost jdk]#javac HelloWorld.java

          沒有錯(cuò)誤

          [root@localhost jdk]#java HelloWorld

          Hello,World

          恭喜,

          Kq[業(yè)Kk0+?bv

          你的JAVA運(yùn)行環(huán)境搭建好了。現(xiàn)在進(jìn)入下一步。

           

          1.2 Mysql數(shù)據(jù)庫安裝

          1下載數(shù)據(jù)庫安裝文件mysql-4.0.15a.tar.gz

          2建立MySLQL的用戶和組

          [root@localhost var]# groupadd mysql

          [root@localhost var]# useradd –g mysql mysql

          3修改根目錄下的root目錄下面的.bash_profile文件添加PATH環(huán)境變量

          [root@localhost root]#vi .bash_profile

          PATH=$PATH:$HOME/bin:/usr/local/mysql/bin

          保存退出

          下面就可以看是安裝MySql

          4解壓縮文件

          [root@localhost jdk]#tar xzvf mysql-4.0.15a.tar.gz

          進(jìn)入解壓縮得到的目錄

          [root@localhost var]# cd mysql-4.0.15a

          5配置發(fā)行版本并且編譯

          [root@localhost mysql-4.0.15a]#./configure --prefix=/usr/local/mysql

          [root@localhost mysql-4.0.15a]#make

          當(dāng)你運(yùn)行configure時(shí),你可能想要指定一些選項(xiàng),--prefix選項(xiàng)制定安裝mysql的目錄為/usr/local/mysql

          6安裝所有東西

          [root@localhost mysql-4.0.15a]#make install

          你可能需要root用戶來運(yùn)行這個(gè)命令

          ok現(xiàn)在mysql數(shù)據(jù)庫服務(wù)器就安裝好了。接下來還有重要的步驟需要執(zhí)行

          7創(chuàng)造MySQL授權(quán)表(只有你以前沒安裝MySQL是必需的):

          [root@localhost mysql-4.0.15a]#scripts/mysql_install_db

          8做完上面的步驟我們就可以啟動(dòng)mysql進(jìn)行操作了。

          [root@localhost mysql-4.0.15a]#cd /usr/local/mysql/bin

          [root@localhost bin]# ./mysqld_safe &

          如果沒有出錯(cuò)提示,查看一下mysql進(jìn)程

          [root@localhost bin]# ps aux|grep mysql

          root      1205 0.0 0.0 5388 168 ?        S    Apr22   0:00 /bin/sh /usr/loca

          l/mysql/bin/mysqld_safe

          mysql     1227 0.0 1.3 100316 13756 ?      S    Apr22   0:36 [mysqld]

          root     22956 0.0 0.0 4816 640 pts/1    S    10:41   0:00 grep mysql

          粗體的就是mysql的進(jìn)程了。

          現(xiàn)在就可以使用mysql,root登陸MySQL服務(wù)器

          [root@localhost bin]#mysql –u root –p

          這里會(huì)提示輸入密碼默認(rèn)的root用戶密碼為空。直接回車就可以了

          Enter password:

          Welcome to the MySQL monitor. Commands end with ; or \g.

          Your MySQL connection id is 95 to server version: 4.0.15a-log

           

          Type ''''help;'''' or ''''\h'''' for help. Type ''''\c'''' to clear the buffer.

           

          mysql>

          這樣就成功登陸MySQL服務(wù)器了。

          mysql> show databases;

          +----------+

          | Database |

          +----------+

          | cumt     |

          | mysql    |

          | test     |

          +----------+

          3 rows in set (0.01 sec)

          現(xiàn)在就可以建立數(shù)據(jù)庫了。這里就不介紹怎樣建立數(shù)據(jù)庫和建立表了。

          Root用戶沒有密碼是不安全的所以你需要修改root用戶的密碼

          [root@localhost bin]# mysqladmin -u root password ''''new-password''''

          ''''new-password''''換成你自己的密碼就可以了。

          注意:做完上面的我們就可以用mysqlbin目錄下的mysql來管理數(shù)據(jù)庫了。可是這還沒法在程序中使用數(shù)據(jù)庫。我在jsp中連接數(shù)據(jù)庫的時(shí)候出現(xiàn)如下錯(cuò)誤:

          java.sql.SQLException: Data source rejected establishment of connection, message from server: "Host ''''localhost.localdomain''''is not allowed to connect to this MySQL server"

          這是MySQL的權(quán)限問題具體的講解請(qǐng)參考MySQL參考手冊(cè)的存取權(quán)限系統(tǒng)

          我們需要做的是讓root可以從localhost連接到數(shù)據(jù)庫,

          ,G專}Uc{|g[

          你也可以建立一個(gè)新用戶來連接

           

          登陸到mysql服務(wù)器

          mysql> GRANT ALL PRIVILEGES ON *.* TO cumt@localhost
                     IDENTIFIED BY ''''cumt'''' WITH GRANT OPTION;
          mysql> GRANT ALL PRIVILEGES ON *.* TO cumt@"%"
                     IDENTIFIED BY ''''cumt'''' WITH GRANT OPTION;

          這兩天語句添加用戶cumt使得它可以從任何地方連接服務(wù)器的一個(gè)完全的超級(jí)用戶,但是必須使用一個(gè)口令cumt做這個(gè)。現(xiàn)在我們就可以從程序中用cumt來連接數(shù)據(jù)庫了。

          但是在程序中還是沒有對(duì)表的寫權(quán)限。這是由于我們的數(shù)據(jù)庫用戶是root而不是我們建立的mysql組的mysql用戶。所以只有讀的權(quán)限而沒有寫的權(quán)限。我們需要把mysql目錄下面的var目錄下面的目錄和文件的所有者改成mysql組的myql用戶:

          [root@localhost bin]#chown -R mysql /usr/local/mysql/var

          [root@localhost bin]#cp support- files/my-medium.cnf /etc/my.cnf

          好了做完上面的我們就可以在程序中使用cumt連接數(shù)據(jù)庫和操作數(shù)據(jù)庫了。

          1.3 Web服務(wù)器tomcat安裝配置

          下載tomcat,

          件_:87VNLPI$

          jakarta-tomcat-4.1.18.tar.gz

           

          解壓縮

          root@localhost var]#tar xzvf jakarta-tomcat-4.1.18.tar.gz
          為方便操作:
          ln -s jakarta-tomcat-4.0.1 tomcat
          ln -s j2sdk1.4.0 jdk

          修改Tomcat/bin/startup.sh :

          export JAVA_HOME=/usr/local/jdk
          export CLASSPATH=$JAVA_HOME/lib:$JAVA_HOME/jre/lib:.
          export PATH=$PATH:$JAVA_HOME/bin:$JAVA_HOME/jre/bin

          /usr/local/tomcat/bin/catalina.sh start


          啟動(dòng)Tomcat/bin/startup.sh
          1.使用ps -ax | grep tomcat可以查詢出內(nèi)存中存在tomcat程序
          使用http://你的服務(wù)器域名或IP地址或localhost:8080/可訪問

          2.
          如要求系統(tǒng)開機(jī)自動(dòng)tomcat /etc/rc.d/rc.local中加入:
          /usr/local/tomcat/bin/startup.sh

          3.
          對(duì)于linux7.1系統(tǒng), tomcat好象不能正常啟動(dòng),需要安裝:
          compat-libstdc++-6.2-2.9.0.14.i386.rpm

          rpm -i compat-libstdc++-6.2-2.9.0.14.i386.rpm

          4.
          如果希望以http://www.xxx.com:8080/myweb 訪問自己的jsp程序,以下步驟:
          (1).
          在任意地方建立目錄myweb 比如 /home/bqlr/myweb
          (2).
          myweb下建立 WEB-INF WEB-INF/classes目錄
          (3).
          tomcatconf目錄下修改server.xml:

          <!-- Tomcat Manager Context -->
          <Context path="/manager" docBase="manager" debug="0" privileged="true"/>

          <!--
          下面是自己加入的-->

          <Context path="/myweb" docBase="/home/bqlr/myweb" debug="0" reloadable="true"/>

          (4) tomcatwebapps目錄下,建立目錄連接myweb
          ln -s /home/bqlr/myweb /usr/local/tomcat/webapps/myweb

          (5)
          重新啟動(dòng)Tomcat. class文件放在/home/bqlr/myweb/WEB-INF/classes目錄下 jsp文件放在/home/bqlr/myweb

          posted @ 2007-05-14 10:49 edsonjava 閱讀(394) | 評(píng)論 (0)編輯 收藏
           

          jfreechart是一個(gè)免費(fèi)(文檔收費(fèi)40$)創(chuàng)建圖片的java工具.可以創(chuàng)建如下圖形:
          餅圖(pie charts;)
          曲線圖(line charts )
          柱狀圖(horizontal/vertical bar charts)
          甘特圖(Gantt charts; )
          XY plots and scatter plots;
          time series, high/low/open/close charts and candle stick charts;
          combination charts;
          Pareto charts;
          bubble charts;
          wind plots, meter charts and symbol charts;

          必看的幾個(gè)貼:

          http://www-900.ibm.com/developerWorks/cn/wsdd/library/techarticles/yangyaping0307/waslinux.shtml

          http://www.lslnet.com/linux/docs/linux-2940.htm

          http://blog.blogchina.com/article_81038.344426.html

          在向linux移植的時(shí)候會(huì)顯示不出中文,出現(xiàn)方塊。

          解決方法:copy或引用/usr/share/fonts/zh_CN/TrueType目錄下的中文字體,修改/home/jdk/jre/lib/fonts.properties

          配置文件,如下:

          sansserif.0=-misc-ZYSong18030-medium-r-normal--*-%d-*-*-c-*-iso10646-1

          sansserif.italic.0=-misc-ZYSong18030-medium-r-normal--*-%d-*-*-c-*-iso10646-1

          sansserif.bold.0=-misc-ZYSong18030-medium-r-normal--*-%d-*-*-c-*-iso10646-1

          sansserif.bolditalic.0=-misc-ZYSong18030-medium-r-normal--*-%d-*-*-c-*-iso10646-1

          # Component Font Character Encodings
          #
          fontcharset.serif.0=sun.io.CharToByteISO8859_1
          fontcharset.serif.1=sun.awt.motif.CharToByteX11GBK

          fontcharset.sansserif.0=sun.io.CharToByteISO8859_1
          fontcharset.sansserif.1=sun.awt.motif.CharToByteX11GBK

          fontcharset.monospaced.0=sun.io.CharToByteISO8859_1
          fontcharset.monospaced.1=sun.awt.motif.CharToByteX11GBK

          fontcharset.dialog.0=sun.io.CharToByteISO8859_1
          fontcharset.dialog.1=sun.awt.motif.CharToByteX11GBK

          fontcharset.dialoginput.0=sun.io.CharToByteISO8859_1
          fontcharset.dialoginput.1=sun.awt.motif.CharToByteX11GBK

          fontset.sansserif.plain=\
          -misc-ZYSong18030-medium-r-normal--*-%d-*-*-c-*-iso10646-1

          fontset.sansserif.italic=\
          -misc-ZYSong18030-medium-r-normal--*-%d-*-*-c-*-iso10646-1

          fontset.sansserif.bold=\
          -misc-ZYSong18030-medium-r-normal--*-%d-*-*-c-*-iso10646-1

          fontset.sansserif.bolditalic=\
          -misc-ZYSong18030-medium-r-normal--*-%d-*-*-c-*-iso10646-1


          fontset.default=\
          -misc-ZYSong18030-medium-r-normal--*-%d-*-*-c-*-iso10646-1


          appendedfontpath=/usr/share/fonts/zh_CN/TrueType

          然后對(duì)自己基于jfreechart寫的程序進(jìn)行重新編譯、運(yùn)行:

          javac -encoding GBK   BarChartDemo.java //以GBK進(jìn)行編碼
          java -Djava.awt.headless=true BarChartDemo//使用awt時(shí)不用調(diào)用x11的圖形環(huán)境

           

          在tomcat使用jfreechart時(shí)

          tomcat是5.0,在redhat8上,未啟動(dòng)X,方法如下:
          1)終止你的tomcat。即:
          tomcat目錄/bin/shutdown.sh
          2)設(shè)置環(huán)境變量:
          CATALINA_OPTS="-Djava.awt.headless=true"
          export CATALINA_OPTS
          (如果你想每次開機(jī)自動(dòng)生效,則可把這兩句寫入系統(tǒng)或者你的賬號(hào)啟動(dòng)sh的.profile里)
          3)啟動(dòng)你的tomcat。即:
          tomcat目錄/bin/startup.sh

          用的Web服務(wù)器resin時(shí),

          修改resin/bin/下面的wrapper.pl中的一行
          $JAVA_ARGS="-Djava.awt.headless=true";


           

          ==============================================================

          最近項(xiàng)目中用到j(luò)freechart,使用的版本是:jfreechart-0.9.8。
          如果不進(jìn)行相關(guān)的font的設(shè)置,生成的統(tǒng)計(jì)圖表顯示的中文非常模糊。
          做了一個(gè)例子,可以解決這個(gè)問題。

          [該方法是將一般字體替換為“黑體”使中文更加清楚,

          因此必須保證你的OS上有該字體,并且jdk能夠識(shí)別得到]

          核心代碼如下:

          JFreeChart chart = ChartFactory.createVerticalBarChart3D(title, domain, range, dataset,true,true,false);

          chart.setBackgroundPaint(new GradientPaint(0.0F, 0.0F, Color.white, 1000F, 0.0F, Color.red));
          chart.setTitle(new TextTitle(title, new Font("隸書", Font.ITALIC, 15)));

          Font font=new Font("黑體",Font.TRUETYPE_FONT, 12);

          StandardLegend legend = (StandardLegend) chart.getLegend();
          legend.setItemFont(font);

          CategoryPlot plot = (CategoryPlot)chart.getPlot();
          plot.setForegroundAlpha(0.9F);

          CategoryAxis domain_axis = plot.getDomainAxis();
          domain_axis.setTickLabelFont(font);

          ValueAxis value_axis=plot.getRangeAxis();
          value_axis.setTickLabelFont(font);


          posted @ 2007-04-12 15:53 edsonjava 閱讀(262) | 評(píng)論 (0)編輯 收藏
           
          主站蜘蛛池模板: 新闻| 玉林市| 三明市| 高邑县| 凌海市| 临泽县| 岳池县| 恭城| 靖江市| 舟曲县| 宝清县| 叙永县| 北流市| 固始县| 清新县| 高要市| 天门市| 平原县| 安庆市| 盐池县| 鲁甸县| 新密市| 娱乐| 信宜市| 津南区| 中西区| 岳阳县| 故城县| 白玉县| 鄂尔多斯市| 塔河县| 新疆| 古丈县| 武乡县| 建阳市| 鹤山市| 连山| 东阳市| 古丈县| 东莞市| 文昌市|