2007年5月14日

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

          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>



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

          下面是一個簡單的模型定義的例子:
          <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>

          這個模型包含了一個銷售cube,這個cube有兩個維,時間和性別維;兩個度量,銷售數量和銷售總額。
          我們可以在這個模型上寫一個 MDX 查詢:
          select {[Measures].[Unit Sales], [Measures].[Store Sales]} on columns,
          {[Time].[1997].[Q1].descendants} on rows
          from [Sales]
          where [Gender].[F]
          這 個查詢涉及到了銷售立方體, 每一個維 [Measures], [Time], [Gender], 這些維的多個成員. 結果如下:
          [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

          下面詳細地介紹一下模式定義:
          一個立方體是一個或者多個維和度量的集合,通常是一個事實表,這里是 ‘sales_fact_1997". 事實表保存了需要計算的列和包含維的參考表.
          <Cube name="Sales">
          <Table name="sales_fact_1997"/>
          ...
          </Cube>
          這里用 <Table> 元素定義事實表. 如果事實表 不在默認的模式中, 你可以用"schema"屬性指定一個明確地模式,例如:
          <Table schema="foodmart" name="sales_fact_1997"/>
          你也可以利用 <View> 和 <Join> 結構來創建更復雜的sql .
          度量
          銷售立方體定義了兩個維 "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"/>
          每個度量有一個名字,對應事實表中的一列, 采用一個聚集函數 (usually "sum").
          一個可選的格式字符串指定了值如何被打印. 這里我們選擇銷售數量不帶小數的輸出(因為銷售數量是整數) ,銷售總額帶2位小數 . 符號',' 和 '.' 是對地區敏感的, 因此如果是在意大利運行, 銷售總額可能會出現 "48.123,45". 你可以用 advanced format strings來實現更嚴格的效果.度量值不是從列中來的,而是從立方體的單元中來的

          性別維由單一的層次組成,僅有一層。
          <Dimension name="Gender" foreignKey="customer_id">
          <Hierarchy hasAll="true" primaryKey="customer_id">
          <Table name="customer"/>
          <Level name="Gender" column="gender" uniqueMembers="true"/>
          </Hierarchy>
          </Dimension>
          對于任意給定的銷售, 性別維是指購買改產品的客戶的性別. 它通過連接事實表"sales_fact_1997.customer_id"和維表"customer.customer_id"
          來表示 。"gender" 包括兩個值, 'F' 和 'M', 因此性別維包含的成員: [Gender].[F] and [Gender].[M]. 因為 hasAll="true", 系統產生一個特別的 'all' 層, 僅包括一個成員 [All Genders].
          一個維可以包含多個層次:
          <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>
          第一個層次沒有指定名稱.缺省的情況下,一個層次擁有和它的維相同的名稱。,因此第一個層次成為"Time".這些層次沒有太多的共同之處,他們甚至沒有相同的表,除非它們連接了實施表中的同一列"time_id"。在一個維上存在兩個層次的原因是這樣對最終用戶是有用的. 如果一個維上存在兩個層次, MDX會強制不允許在一個查詢中同時用到他們.
          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>
          每個維包含有多層組成的一個層次,

          大多數維都是僅有一個層次,但有時候一個維有多個層次。比如:你可能希望在時間維上從天聚集到月,季度和年;或者從天聚集到周和年。這兩種層次都是從天到年,但是聚集的路徑不同。大多數層次有全成員,全成員包括層次的所有成員,因此能夠代表他們的總合。它通常命名為'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>
          這里定義一個 "Product" 維 由三個表構成. 事實表連接 表"product" (通過外鍵 "product_id"),表"product"連接表"product_class" (通過外鍵 "product_class_id"),表"product_class"連接表 "product_type" (通過外鍵 "product_type_id"). 我們利用 <Join> 元素的循環嵌套, <Join>帶有兩個操作對象; 操作對象可能是表,連接或者查詢 。
          按照操作對象行的數目來安排次序,表 "product" 的行數最大, 因此它首先出現連接事實表;然后是表 "product_class"和 "product_type",在雪花的末端擁有的行數最小.
          注意外部元素 <Join>有一個屬性 rightAlias. 這是必要的,因為join 的右邊(是內部元素 <Join> ) 有可能是許多表組成的.這種情況下不需要屬性leftAlias,因為列 leftKey 很明確的來自表 "product".

          共享維
          當為一個連接生成SQL的時候, mondrian 需要知道連接哪一個列. 如果一正在連接一個多表連接, 你需要告訴它連接這些表里的哪一個表,哪一個列.
          因為共享維不屬于一個cube,你必須給它們一個明確的表 (或者數據源). 當你在一個特別的cube里用他們的時候, 你要指定外鍵 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
          父子層次
          一個使用方便的層次 有一個嚴格的層的集合, 成員與層緊密的聯系.比如,在 Product 層次中, 任何產品名稱層的成員在商標層上都有一個父親 ,商標層上的成員在產品子目錄層也都有一個父親. 這種結構對于現實世界中的數據有時候太嚴格了.
          一個父子層次只有一層 (不計算 'all' 層), 但是任何成員可以在同一層上有父親成員. 一個典型的例子是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 是一個成員連接到它父親成員的列名。在這種情況下, 它是指向雇員經理的外鍵。元素<Level>的子元素 <ParentExpression> 是與屬性 parentColumn 有相同作用的,但是元素允許定義任意的SQL表達式, 就像元素 <Expression>. 屬性 parentColumn (或者 元素<ParentExpression>) 是維一向Mondrian指出 層次有父子結構的。
          屬性 nullParentValue 是指明成員沒有父成員的值 。 缺省情況下 nullParentValue="null", 但是因為許多數據庫不支持null, 建模時 用其他值來代替空值,0和-1.

          物理結構
          member reade
          member reader 是訪問成員的方法. 層次通常以維表為基礎建立的 , 因此要用sql來構造.但是甚至你的數據沒有存在于 RDBMS, 你可以通過一個 Java 類來訪問層次。(自定義 member reader)
          Here are a couple of examples:
          DateSource (to be written)生成一個時間層次. 按常規,數據倉庫工具生成一個表 ,每天包含一行。但是問題是這個表需要裝載,并且隨著時間的變化能夠添加更多的行。 DateSource 在內存中按照要求生成日期成員.
          FileSystemSource (to be written) 按照目錄和文件的層次描述文件系統。 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) 創建了一個基于表達式的層次。
          自定義member reader 必須實現接口 mondrian.rolap.MemberSource. 如果你需要實現一個更大的成員操作集合, 需要實現接口 interface mondrian.rolap.MemberReader; 否則, Mondrian在 mondrian.rolap.CacheMemberReader中封裝 你的 reader類.你的 member reader 必須有一個公共的構造函數,這個構造函數擁有參數(Hierarchy,Properties),拋出未檢查的錯誤.
          Member readers 用 元素<Hierarchy> 的屬性memberReaderClass來聲明; 任何 <Parameter> 子元素通過屬性構造函數來傳遞.
          這是一個例子:
          <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" 實現了接口interface mondrian.olap.CellReader.


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

          對JPivot的jfreechart和drillthrough顯示做了增強,終于可以拿出去給人用了。

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

           

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

           

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


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


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


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

          我在用Jpivot的時候,發現用mondrian是影響取數性能其中的一個瓶頸........ 經研究.....我們自己修改了jpivot和wcf的一些代碼來適應我們自己的項目.........以下是我做的一些修改.....想聽聽大家的意見 1.脫離mondrian.直接寫dll的方式取數,然后生成XML數據 .我發現脫離mondrian自己寫了一個DLL去調用MSSQL 2000 的OLAP,數度很快........... 2 .修改界面的顯示方式 上面也說道.Jpivot的界面一個不好看,二是用起來很不方便.比如取維度等的時候....一層一層的進去實在很麻煩.... a.修改取維度的方式 我們參照ms的做法 做成一個了一個樹的取數,研究jpivot里面的代碼.如果直接用jpivot的代碼取數據十分慢.這樣我自己通過AJAX和Jpivot結合,動態生成樹的結構,然后在樹上取維度的時候,直接通過鼠標托到選擇維度textbox上.........依照條件生成相應MDX....顯示數據..... b.修改數據顯示的樣式.和取維度,生成MDX分開了. 顯示數據我用了另外一種方式顯示.就是用Frame分為上下兩層.....上下兩層可以通過按鈕擴大整個頁面........ 3. 集成在自己的框架中 集成在自己的框架中,我個人覺得是比較麻煩的一件事情.一點小事沒有搞好就很麻煩...因為我們是用JSF開發的.所以依照Jpivot....自己寫了一些組件來輔助開發,我自己開發主要改成比較像ms 2000 的olap分析方法... 還未完成的需求 JFreeChar的功能還需要加強. 個人感覺:jpivot是很不錯.可是不能一拿來就用..我發現好多人用jpivot都要修改好多東西....但是修改起來又比較麻煩....java,j2ee,xml ,xslt,javascript,taglib.....好多東西都要懂.....
          posted @ 2008-03-26 22:28 edsonjava 閱讀(2244) | 評論 (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) | 評論 (0)編輯 收藏
           
          近在論證java領域的開源BI前端框架,把隨手記得東西和大家分享下.
          因為只看了幾天,有沒時間整理所以看起來比較亂,也不是很深入。

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

           他們都有自己的強項和不足,下面簡要介紹下:

          輕量級的:

          OpenI使用Mondrian和Jpivot框架,報表引擎是jasper report,數據挖掘接口是R-Project,

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

           JASPER intelligence也是個輕型項目,對jasper report的支持最好,所以報表部分比較好。

           重量級的:

          PentahospagoBi是兩個比較大的框架了,集成了相當多的開源項目,JfreeReport、Mondrian、Kettle、Weka基本都使用了。特別適合大型復雜項目的開發。

                Pentaho在中國使用的比較多,文檔什么的也多一點。尤其值得一提的是網絡上對他的中文支持做的相當好,很多志愿者翻譯了它的文檔。這給我們開發帶來很大便利。

           

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

          而且Pentaho得到了很大的投資,開發后勁很大,而且會有付費的官方發售版本。 

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

          這個是Pentaho源代碼閱讀報告》,介紹Pentaho構架相當的全面。 

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

          Pentaho相對spagoBi來說功能較強,尤其是工作流一塊做的相當不錯。

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

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

          的demos里展現了spagoBi很多功能。

           后記
          這幾款BI框架因為都是開源的前端框架,所以核心部分使用的還是一些開源項目,

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

          但是期望在原始功能上添加性能功能是比較麻煩的,為了一個新加的功能可能需要相當長的時間來實現。

          另外這些開源框架的權限管理都不怎么強,可能需要改造。

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

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

          我們都知道“瞎子摸象”的故事。不同的瞎子對大象的認識不同,因為他們只認識了自己摸到的地方。而企業如果要避免重犯這樣的錯誤,那就離不開商務智能(BI)。專家認為,BI對于企業的重要性就像聰明才智對于個人的重要性。歐美企業的經驗也證明,企業避免無知和一知半解危險的有效手段就是商務智能。商務智能旨在充分利用企業在日常經營過程中收集的大量數據和資料,并將它們轉化為信息和知識來免除各種無知狀態和瞎猜行為。   

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

          ETL 工具

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

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

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

          OLAP服務器

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

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

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

          ● 計算層:分析、驗證、執行MDX查詢。

          ● 聚集層:一個聚集指內存中一組計算值(cell),這些值通過維列來限制。計算層發送單元請求,如果請求不在緩存中,或者不能通過旋轉聚集導出的話,那么聚集層向存儲層發送請求。聚合層是一個數據緩沖層,從數據庫來的單元數據,聚合后提供給計算層。聚合層的主要作用是提高系統的性能。

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

          OLAP客戶端

          JPivot是JSP風格的標簽庫,用來支持OLAP表,使用戶可以執行典型的OLAP操作,如切片、切塊、上鉆、下鉆等。JPivot使用Mondrian服務器,分析結果可以導出為Excel或PDF文件格式。

          數據庫管理系統

          主要的開源工具包括MonetDB、MySQL、MaxDB和PostgreSQL等。這些數據庫都被設計用來支持BI環境。MySQL、MaxDB和PostgreSQL均支持單向的數據復制。BizGres項目的目的在于使PostgreSQL成為數據倉庫和 BI的開源標準。BizGres為BI環境構建專用的完整數據庫平臺。

          完整的BI開源解決方案

          1.Pentaho 公司的Pentaho BI 平臺

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

          BI平臺包括一個 BI 框架、BI 組件、一個 BI 工作臺和桌面收件箱。BI 工作臺是一套設計和管理工具,集成到Eclipse環境。這些工具允許商業分析人員或開發人員創建報表、儀表盤、分析模型、商業規則和 BI 流程。Pentaho BI 平臺構建于服務器、引擎和組件的基礎之上,包括J2EE 服務器、安全與權限控制、portal、工作流、規則引擎、圖表、協作、內容管理、數據集成、多維分析和系統建模等功能。這些組件的大部分是基于標準的,可使用其他產品替換之。

          2.ObjectWeb

          該項目近日發布了SpagoBi 1.8版本。SpagoBi 是一款基于Mondrain+JProvit的BI方案,能夠通過OpenLaszlo產生實時報表,為商務智能項目提供了一個完整開源的解決方案,它涵蓋了一個BI系統所有方面的功能,包括:數據挖掘、查詢、分析、報告、Dashboard儀表板等等。SpagoBI使用核心系統與功能模塊集成的架構,這樣在確保平臺穩定性與協調性的基礎上又保證了系統具有很強的擴展能力。用戶無需使用SpagoBI的所有模塊,而是可以只利用其中的一些模塊。

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

          3.Bee項目

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

          Bee項目的特點在于:

          ● 簡單快捷的數據訪問;

          ● 支持預先定義報表和實時查詢;

          ● 通過拖拽方式輕松實現報表定制;

          ● 完整報表的輕松控制;

          ● 以表和圖進行高質量的數據展示。

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

          OLAP:Mondrian&JPviot


          olap:online analytical processing(聯機分析處理),實時的分析大量數據,其操作通常是 只讀的.online意味著即使是大量的數據,系統對查詢的響應也要足夠快.

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

          mondrian包含4層:表示層,計算層,聚集層,存儲層.

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

          計算層:分析,驗證,執行MDX查詢.

          聚集層:一個聚集指內存中一組計算值(cell),這些值通過維列來限制.計算層發送單元請求,如果請求不在緩存中,或者不能通過旋轉聚集導出的話,聚集層向存儲層發送請求.

          聚合層是一個數據緩沖層,從數據庫來的單元數據,聚合后提供給計算層。聚合層的主要作用是提高系統的性能。

          存儲層:提供聚集單元數據和維表的成員,這些層可以不在同一機子上,但是計算和聚集層必須在同一臺機子上.

          三種需要存儲的數據:1:事實數據2:聚集3:維

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

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

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

          下面是我關于jpviot的修改:jpviot是顯示mondrian的一個taglib

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

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

          然后可以這樣描述Measure:

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

          所有帶name屬性的都可以替換成中文,jpviot會自動顯示這些中文.

          問題2:關于去掉Measure標題的問題:

          默認生成的報表中會有這么一行
          <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>

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

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

          將其構造函數中的setHierarchyHeader的參數修改為setHierarchyHeader(NO_HEADER);這個函數支持3個參數,我們修改后就不會顯示那個標題行了.

          問題3:生成圖表后自動生成chart表的問題:

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

          最終修改代碼如下:

          com.tonbeller.jpivot.chart.ChartComponent:

          在render函數中修改如下:

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

          問題4:修改jfreechart中的默認字體:

          com.tonbeller.jpivot.chart.ChartComponent中定義了幾種字體,但是這幾種字體都是英文字體,我將其修改為宋體:
          把所有的字體定義都改為"SimSun"
          注意到這兒并沒有玩,如果你僅僅修改程序,仍舊會出現問題,報錯說沒有適合"SimSun"的item
          同時要修改一個配置文件:WEB-INFjpivotchartchartpropertiesform.xml
          在這個配置文件中將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>

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

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

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

          解決方法:
          1.指定輸出文檔類型為xml文檔  (example:data.xsl)
           <xsl:output method="xml"  encoding="gb2312" media-type="text/xml" />
          2.在新的窗口打開,給聯接增加屬性,指明目標窗口為其他窗口  (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數據</word>
           </search>
           <search>
            <url>http://www1.baidu.com/baidu?word=</url>
            <word>xml數據</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=" <!-- 去掉下面一句,將出現錯誤 -->
          <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>
            <!-- 去掉下面一句,將出現錯誤 -->
            <xsl:attribute name="target">_blank</xsl:attribute>
            <xsl:value-of select="word" />
           </xsl:element>
           <br />
          </xsl:template>

          </xsl:stylesheet>

          posted @ 2008-02-23 23:08 edsonjava 閱讀(531) | 評論 (0)編輯 收藏
           
          軟件包:javax.servlet.http 
                所包含的接口:HttpServletRequest;HttpServletResponse;HttpSession;HttpSessionBindingListener;HttpSessionContext。
                所包含的類:Cookie;HttpServlet;HttpSessionBindingEvent;HttpUtils。

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

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

                二、HttpServletResponse接口
                定義\

                public interface HttpServletResponse extends ServletResponse
                描述一個返回到客戶端的HTTP回應。這個接口允許Servlet程序員利用HTTP協議規定的頭信息。
                成員變量
                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產狀態碼是由HTTP/1.1定義的。
                方法
                1、addCookie
                public void addCookie(Cookie cookie);
                在響應中增加一個指定的cookie。可多次調用該方法以定義多個cookie。為了設置適當的頭域,該方法應該在響應被提交之前調用。
                2、containsHeader
                public boolean containsHeader(String name);
                檢查是否設置了指定的響應頭。
                3、encodeRedirectURL
                public String encodeRedirectURL(String url);
                對sendRedirect方法使用的指定URL進行編碼。如果不需要編碼,就直接返回這個URL。之所以提供這個附加的編碼方法,是因為在redirect的情況下,決定是否對URL進行編碼的規則和一般情況有所不同。所給的URL必須是一個絕對URL。相對URL不能被接收,會拋出一個IllegalArgumentException。
                所有提供給sendRedirect方法的URL都應通過這個方法運行,這樣才能確保會話跟蹤能夠在所有瀏覽器中正常運行。
                4、encodeURL
                public String encodeURL(String url);
                對包含session ID的URL進行編碼。如果不需要編碼,就直接返回這個URL。Servlet引擎必須提供URL編碼方法,因為在有些情況下,我們將不得不重寫URL,例如,在響應對應的請求中包含一個有效的session,但是這個session不能被非URL的(例如cookie)的手段來維持。
                所有提供給Servlet的URL都應通過這個方法運行,這樣才能確保會話跟蹤能夠在所有瀏覽器中正常運行。
                5、sendError
                public void sendError(int statusCode) throws IOException;
                public void sendError(int statusCode, String message) throws
                   IOException;
                用給定的狀態碼發給客戶端一個錯誤響應。如果提供了一個message參數,這將作為響應體的一部分被發出,否則,服務器會返回錯誤代碼所對應的標準信息。
                調用這個方法后,響應立即被提交。在調用這個方法后,Servlet不會再有更多的輸出。
                6、sendRedirect
                public void sendRedirect(String location) throws IOException;
                使用給定的路徑,給客戶端發出一個臨時轉向的響應(SC_MOVED_TEMPORARILY)。給定的路徑必須是絕對URL。相對URL將不能被接收,會拋出一個IllegalArgumentException。
                這個方法必須在響應被提交之前調用。調用這個方法后,響應立即被提交。在調用這個方法后,Servlet不會再有更多的輸出。
                7、setDateHeader
                public void setDateHeader(String name, long date);
                用一個給定的名稱和日期值設置響應頭,這里的日期值應該是反映自1970-1-1日(GMT)以來的精確到毫秒的長整數。如果響應頭已經被設置,新的值將覆蓋當前的值。
                8、setHeader
                public void setHeader(String name, String value);
                用一個給定的名稱和域設置響應頭。如果響應頭已經被設置,新的值將覆蓋當前的值。
                9、setIntHeader
                public void setIntHeader(String name, int value);
                用一個給定的名稱和整形值設置響應頭。如果響應頭已經被設置,新的值將覆蓋當前的值。
                10、setStatus
                public void setStatus(int statusCode);
                這個方法設置了響應的狀態碼,如果狀態碼已經被設置,新的值將覆蓋當前的值。
                以下的幾個方法將被取消\
                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);
                這個方法設置了響應的狀態碼,如果狀態碼已經被設置,新的值將覆蓋當前的值。如果提供了一個message,它也將會被作為響應體的一部分被發送。

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

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

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

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

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

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

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

          2  Jive與設計模式

          Jive論壇系統使用大量設計模式巧妙地實現了一系列功能。因為設計模式的通用性和可理解性,將幫助更多人很快地理解 Jive論壇源碼,從而可以依據一種“協定”來動態地擴展它。那么使用設計模式還有哪些好處?

          2.1  設計模式

          設計模式是一套被反復使用、多數人知曉的、經過分類編目的、代碼設計經驗的總結。使用設計模式是為了可重用代碼、讓代碼更容易被他人理解、保證代碼可靠性。毫無疑問,設計模式于己于他人于系統都是多贏的。設計模式使代碼編制真正工程化,設計模式是軟件工程的基石。

          GOF(設計模式作者簡稱)《設計模式》這本書第一次將設計模式提升到理論高度,并將之規范化,該書提出了23種基本設計模式。自此,在可復用面向對象軟件的發展過程中,新的大量的設計模式不斷出現。

          很多人都知道Java是完全面向對象的設計和編程語言,但是由于接受教育以及經驗的原因,大多數程序員或設計人員都是從傳統的過程語言轉變而來,因此在思維習慣上要完全轉變為面向對象的設計和開發方式是困難的,而學習設計模式可以更好地幫助和堅固這種轉變。

          凡是學習完成設計模式的人都有一種類似重生的感覺,這種重生可以從很多方面去解釋。換一種新的角度來看待和解決問題應該是一種比較貼切的解釋,而這種新的思維角度培養屬于基礎培訓,因此,設計模式是學習Java的必讀基礎課程之一。

          由于設計模式概念比較抽象,對于初學者學習有一定的難度,因此結合Jive論壇系統學習設計模式將是一種很好的選擇。

          掌握了設計模式,將會幫助程序員或設計人員以更加可重用性、可伸縮性的眼光來開發應用系統,甚至開發通用的框架系統。框架系統是構成一類特定軟件可復用設計的一組相互協作的類,主要是對應用系統中反復重用部分的提煉,類似一種模板,這是一種結構性的模板。

          框架通常定義了應用體系的整體結構、類和對象的關系等設計參數,以便于具體應用實現者能集中精力于應用本身的特定細節。框架強調設計復用,而設計模式最小的可重用單位,因此框架不可避免地會反復使用到設計模式。關于通用框架系統的設計開發將在以后章節中討論。

          其實Jive論壇本身也形成了一個基于Web結構的通用框架系統,因為它很多設計思想是可以重用的,例如設定 一個總體入口,通過入口檢查用戶的訪問控制權限,當然還有其他各方面的功能實現方式都是值得在其他系統中借鑒的,也正因為它以模式的形式表現出來,這種可 重用性和可借鑒性就更強。

          2.2  ForumFactory與工廠模式

          工廠模式是GOF設計模式的主要常用模式,它主要是為創建對象提供了一種接口,工廠模式主要是封裝了創建對象的細節過程,從而使得外界調用一個對象時,根本無需關心這個對象是如何產生的。

          在GOF設計模式中,工廠模式分為工廠方法模式和抽象工廠模式。兩者主要區別是,工廠方法是創建一種產品接口下的產品對象,而抽象工廠模式是創建多種產品接口下的產品對象,非常類似Builder生成器模式。在平時實踐中,使用較多的基本是工廠方法模式。

          以類SampleOne為例,要創建SampleOne的對象實例:

          SampleOne sampleOne = new SampleOne();

          如果Sample類有幾個相近的類:SampleTwo或SampleThree,那么創建它們的實例分別是:

          SampleTwo sampleTwo = new SampleTwo();

          SampleThree sampleThree = new SampleThree();

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

          SampleIF sampleOne = new SampleOne();

          SampleIF sampleTwo = new SampleTwo();

          SampleIF sampleThree = new SampleThree();

          在實際情況中,有時并不需要同時生成3種對象,而是根據情況在3者之中選一個。在這種情況下,需要使用工廠方法來完成了,創建一個叫SampleFactory的抽象類:

          public class SampleFactory{

             public abstract SampleIF creator();

          }

          在這個抽象工廠類中有一個抽象方法creator,但是沒有具體實現,而是延遲到它的子類中實現,創建子類SampleFactoryImp:

          public class SampleFactoryImp extends SampleFactory{

             public SampleIF creator(){

              //根據其他因素綜合判斷返回具體產品

              //假設應該返回SampleOne對象

                 return new SampleOne();

          }

          }

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

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

          前面已經討論在Jive中設置了論壇統一入口,這個統一入口就是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 單態模式,將在2.3節討論

              if (factory == null) {

                synchronized(initLock) {

                  if (factory == null) {

                      ... //從配置文件中獲得當前className

                    try {

                        //動態裝載類

                        Class c = Class.forName(className);

                        factory = (ForumFactory)c.newInstance();

                    }

                    catch (Exception e) {

                        return null;

                    }

                  }

                }

              }

              //返回 proxy.用來限制授權對forum的訪問

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

            }

            //創鍵產品接口Forum的具體對象實例

            public abstract Forum createForum(String name, String description)

            throws UnauthorizedException, ForumAlreadyExistsException;

             //創鍵產品接口ForumThread的具體對象實例

          public abstract ForumThread createThread(ForumMessage rootMessage)

          throws UnauthorizedException;

          //創鍵產品接口ForumMessage的具體對象實例

           

           

              public abstract ForumMessage createMessage();

            ....

          }

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

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

          因此,Jive論壇在統一入口處使用了抽象工廠模式來動態地創建論壇中所需要的各種產品,如圖3-4所示。

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

          圖3-4中,XmlForumFactory和DbForumFactory作為抽象工廠 ForumFactory的兩個具體實現,而Forum、ForumThread和ForumMessage分別作為3個系列抽象產品接口,依靠不同的工 廠實現方式,會產生不同的產品對象。

          從抽象工廠模式去理解Jive論壇統一入口處,可以一步到位掌握了幾個類之間的大概關系。因為使用了抽象工廠模式這種通用的設計模式,可以方便源碼閱讀者快速地掌握整個系統的結構和來龍去脈,圖3-4這張圖已經初步展示了Jive的主要框架結構。

          細心的讀者也許會發現,在上面ForumFactory有一個getInstance比較令人費解,這將在2.3節進行討論。


          2.3  統一入口與單態模式

          在上面ForumFactory的getInstance方法使用單態(SingleTon)模式。單態模式是保證一個類有且僅有一個對象實例,并提供一個訪問它的全局訪問點。

          前面曾提到ForumFactory是Jive提供客戶端訪問數據庫系統的統一入口。為了保證所有的客戶端請求都要經過這個ForumFactory,如果不使用單態模式,客戶端下列調用語句表示生成了ForumFactory實例:

          ForumFactory factory = new DbForumFactory();

          客戶端每發生一次請求都調用這條語句,這就會發生每次都生成不同factory對象實例,這顯然不符合設計要求,因此必須使用單態模式。

          一般在Java實現單態模式有幾種選擇,最常用而且安全的用法如下:

          public class Singleton {

            private Singleton(){}

            //在自己內部定義自己一個實例,是不是很奇怪

            //注意這是private,只供內部調用

            private static Singleton instance = new Singleton();

            //這里提供了一個供外部訪問本class的靜態方法,可以直接訪問

            public static Singleton getInstance() {

              return instance;

            }

          }

          單態模式一共使用了兩條語句實現:第一條直接生成自己的對象,第二條提供一個方法供外部調用這個對象,同時最好將構造函數設置為private,以防止其他程序員直接使用new Singleton生成實例。

          還有一種Java單態模式實現:

          public class Singleton {

            private Singleton(){}

            private static Singleton instance = null;

            public static synchronized Singleton getInstance() {

              if (instance==null)

                instance=new Singleton()

              return instance;

            }

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

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

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

          Class c = Class.forName(className);

          factory = (ForumFactory)c.newInstance();

          這是利用Java的反射機制,可以通過動態指定className的數值而達到生成對象的方式。

          使用單態模式的目標是為了控制對象的創建,單態模式經常使用在控制資源的訪問上。例如數據庫連接或 Socket連接等。單態模式可以控制在某個時刻只有一個線程訪問資源。由于Java中沒有全局變量的概念,因此使用單態模式有時可以起到這種作用,當然 需要注意是在一個JVM中。

          2.4  訪問控制與代理模式

          仔細研究會發現,在ForumFactory的getInstance方法中最后的返回值有些奇怪。按照單態 模式的概念應該直接返回factory這個對象實例,但是卻返回了ForumFactoryProxy的一個實例,這實際上改變了單態模式的初衷。這樣客 戶端每次通過調用ForumFactory的getInstance返回的就不是ForumFactory的惟一實例,而是新的對象。之所以這樣做是為了 訪問權限的控制,姑且不論這樣做的優劣,先看看什么是代理模式。

          代理模式是屬于設計模式結構型模式中一種,它是實際訪問對象的代理對象,或者影子對象,主要達到控制實際對象的訪問。這種控制的目的很多,例如提高性能等。即遠程代理模式,這種模式將在以后章節討論。

          其中一個主要的控制目的是控制客戶端對實際對象的訪問權限。在Jive系統中,因為有角色權限的分別,對于Forum、ForumThread和FroumMessage的訪問操作必須經過權限機制驗證后才能進行。

          以ForumFactoryProxy中的createForum方法為例,其實ForumFactoryProxy也是FroumFactory的一種工廠實現,它的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();

                  }

          }

          在這個方法中進行了權限驗證,判斷是否屬于系統管理員。如果是,將直接從DbForumFactory對象 factory的方法createForum中獲得一個新的Forum對象,然后再返回Forum的子類代理對象ForumProxy。因為在Forum 中也還有很多屬性和操作方法,這些也需要進行權限驗證。ForumProxy和ForumFactoryProxy起到類似的作用。

          Jive中有下列幾個代理類:

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

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

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

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

          User和Group也有相應的代理類。

          由以上分析看出,每個數據對象都有一個代理。如果系統中數據對象非常多,依據這種一對一的代理關系,會有很多代理類,將使系統變得不是非常干凈,因此可以使用動態代理來代替這所有的代理類,具體實現將在以后章節討論。

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

          迭代(Iterator)模式是提供一種順序訪問某個集合各個元素的方法,確保不暴露該集合的內部表現。迭代模式應用于對大量數據的訪問,Java Collection API中Iterator就是迭代模式的一種實現。

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

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

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

          ResultFilter filter = new ResultFilter();  //設置結果過濾器

             filter.setStartIndex(start);             //設置開始點

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

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

             while(threads.hasNext){

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

             }

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

          上述代碼中主要是從Forum的threads方法獲得迭代器ForumThreadIterator的實 例,依據前面代理模式中分析、研究Forum對象的方法,首先是看ForumProxy中對應方法,然后再看DbForum中對應方法的具體實現。在 ForumProxy中,threads方法如下:

          public ForumThreadIterator threads(ResultFilter resultFilter) {

               ForumThreadIterator iterator = forum.threads(resultFilter);

                return new ForumThreadIteratorProxy(iterator, authorization, permissions);

          }

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

          public ForumThreadIterator threads(ResultFilter resultFilter) {

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

             String query = getThreadListSQL(resultFilter, false); 

             //獲得resultFilter設置范圍內的所有ThreadID集合

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

             //以下是計算查詢區域的開始點和終點

             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是一個查詢結果類,可以對論壇主題Thread和帖子內容Message進行過濾或 排序,這樣就可以根據用戶要求定制特殊的查詢范圍。如查詢某個用戶去年在這個論壇發表的所有帖子,那只要創建一個ResultFilter對象就可以代表 這個查詢要求。

          在上面threads方法代碼中,第一步是先定制出相應的動態SQL查詢語句,然后使用這個查詢語句查詢數據庫,獲得查詢范圍內所有的ForumThread的ID集合,然后在這個ID集合中獲得當前頁面的ID子集合,這是非常關鍵的一步。

          在這關鍵的一步中,有兩個重要的方法getThreadListSQL和getThreadBlock:

          ·          GetThreadListSQL:獲得SQL查詢語句query的值,這個方法Jive實現起來顯得非常地瑣碎。

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

          上面代碼的Threads方法中最后返回的是ForumThreadBlockIterator,它是抽象類 ForumThreadIterator的子類,而ForumThreadIterator繼承了Collection的Iterator,以此聲明自己 是一個迭代器,ForumMessageBlockIterator實現的具體方法如下:

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

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

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

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

          在ForumThreadBlockIterator重要方法getElement中實現了兩個功能:

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

          ·          如果當前遍歷指針在當前頁面之內,根據ID獲得完整的數據對象,實現輸出;

          ForumThreadBlockIterator的getElement方法代碼如下:

          private Object getElement(int index) {

             if (index < 0) {        return null;        }

             // 檢查所要獲得的 element 是否在本查詢范圍內(當前頁面內)

             if (index < blockStart ||

          index >= blockStart + DbForum.THREAD_BLOCK_SIZE) {  

                try {

                    //從緩沖中獲得Forum實例

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

                    //獲得下一頁的內容

                    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;

               // 計算這個元素在當前查詢范圍內的相對位置

               int relativeIndex = index % DbForum.THREAD_BLOCK_SIZE;

               // Make sure index isn't too large

               if (relativeIndex < threadBlock.length) {

                  try {

                      // 從緩沖中獲得實際thread 對象

                      element = factory.cacheManager.threadCache.get(

                                  threadBlock[relativeIndex]);

                  } catch (ForumThreadNotFoundException tnfe) { }

               }

               return element;

          }

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

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

          public Object next() {

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

                      permissions);

          }

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

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

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

          (2)每個頁面視為一個Block,每當進入下一頁時,獲得下一個頁面的所有對象的ID集合。

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

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

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

          public Iterator messages(ResultFilter resultFilter) {

             Iterator iterator = thread.messages(resultFilter);

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

          }

          實現的原理基本相同,返回的都是一個Iterator代理類,在這些代理類中都是進行用戶權限檢驗的。

          Jive中也有關于一次性獲得所有數據,然后遍歷ResultSet的做法。這種做法主要適合一次性查詢數據庫的所有數據,例如查詢當前所有論壇Forum,首先實現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結果放入forumList中

                  }

                }catch (SQLException sqle) {

                  sqle.printStackTrace();

                } finally {

                  …

              }

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

            }

          forums方法是返回一個DatabaseObjectIterator,這個 DatabaseObjectIterator也是一個迭代器,但是實現原理要比ForumThreadBlockIterator簡單。它只提供了一個 遍歷指針,在所有ID結果集中遍歷,然后也是通過ID獲得完整的數據對象。

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

          2.6  過濾器與裝飾模式

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

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

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

          先創建一個接口:

          public interface Work

          {

            public void insert();

          }

          這是一種打樁工作的抽象接口,動作insert表示插入,那么插入什么?下面這個實現表示方形木樁的插入:

          public class SquarePeg implements Work{

            public void insert(){

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

            }

          }

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

          public class Decorator implements Work{

            private Work work;

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

            private ArrayList others = new ArrayList();

            public Decorator(Work work)

            {

              this.work=work;

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

            }

            public void insert(){

              otherMethod();

              work.insert();

            }

            public void otherMethod()

            {

              ListIterator listIterator = others.listIterator();

              while (listIterator.hasNext())

              {

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

              }

          }

          }

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

          Work squarePeg = new SquarePeg();

          Work decorator = new Decorator(squarePeg);

          decorator.insert();

          本例中只添加了一個新的行為(打洞),如果還有很多類似的行為,那么使用裝飾模式的優點就體現出來了。因為可以通過另外一個角度(如組織新的油漆工實現子類)來對這些行為進行混合和匹配,這樣就不必為每個行為創建一個類,從而減少了系統的復雜性。

          使用裝飾模式可以避免在被油漆對象decoratee中包裝很多動態的,可能需要也可能不需要的功能,只要在系統真正運行時,通過油漆工decorator來檢查那些需要加載的功能,實行動態加載。

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

          裝飾模式可以解決這種運行時需要動態增加功能的問題,且看看Jive是如何實現的。

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

          public ForumMessage getMessage(long messageID)

                   throws ForumMessageNotFoundException

          {

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

          }

          這是一種奇怪的委托,大概是因為需要考慮到過濾器功能有意為之吧。那就看看ForumFactory的具體實 現子類DbForumFactory的getMessage功能,getMessage是將數據庫中的ForumMessage對象經由過濾器過濾一遍后 輸出(注:因為原來的Jive的getMessage代碼考慮到可緩存或不可緩存的過濾,比較復雜,實際過濾功能都是可以緩存的,因此精簡如下)。

          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 {

            // 應用全局過濾器

               filterMessage = filterManager.applyFilters(message);

                          Forum forum = getForum(forumID);               

                          //應用本論壇過濾器

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

                      }

                      catch (Exception e) { }

                  return filterMessage;

          }

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

          Work decorator = new Decorator(squarePeg);

          forum.getFilterManager()是從數據庫中獲取當前配置的所有過濾器類。每個Forum都有一套自己的過濾器類,這是通過下列語句實現的:

          FilterManager filterManager =  new DbFilterManager();

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

          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的另外一個子類,被油漆者DbForumMessage通過油漆工ForumMessageFilter增加了一些新的行為和功能(過濾),如圖3-6所示。

          圖3-6  裝飾模式

          這就組成了一個稍微復雜一點的裝飾模式。HTMLFilter實現了HTML代碼過濾功能,而JavaCodeHighLighter實現了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()方法,實際是改變了這兩個原來的行為,這類似前面舉例的方法:

          public void insert(){

              otherMethod();

              work.insert();

          }

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

          在HTMLFilter中還使用了原型(Prototype)模式,原型模式定義是:用原型實例指定創建對象的種類,并且通過復制這些原型創建新的對象。按照這種定義,Java的clone技術應該是原型模式的一個實現。

          HTMLFilter的clone方法實際就是在當前HTMLFilter實例中再生成一個同樣的實例。這樣 在處理多個并發請求時,不用通過同一個過濾器實例進行處理,提高了性能。但是HTMLFilter的clone方法是采取new方法來實現,不如直接使用 Object的native方法速度快。

          因為在DbFilterManager中是根據配置使用類反射機制動態分別生成包括HTMLFilter在內的過濾器實例。但是每種過濾器實例只有一個,為了使得大量用戶不必爭奪一個過濾器實例來實現過濾,就采取了克隆方式,這種實戰手法可以借鑒在自己的應用系統中。

          2.7  主題監測與觀察者模式

          觀察者(Observer)模式是定義對象之間一對多的依賴關系,當一個被觀察的對象發生改變時,所有依賴于它的對象都會得到通知并采取相應行為。

          使用觀察者模式的優點是將被觀察者和觀察者解耦,從而可以不影響被觀察者繼續自己的行為動作。觀察者模式適合應用于一些“事件觸發”場合。

          在Jive中,用戶也許會對某個主題感興趣,希望關于此主題發生的任何新的討論能通過電子郵件通知他,因此他訂閱監視了這個主題。因為這個功能的實現會引入電子郵件的發送。在前面章節已經討論了電子郵件發送有可能因為網絡原因延遲,如果在有人回復這個主題時,立即進行電子郵件發送,通知所有訂閱該主題的用戶。那么該用戶可能等待很長時間得不到正常回應。

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

          看看Jive的WatchManager類:

          public interface WatchManager {

              //正常監察類型,用戶在這個主題更新后再次訪問時,會明顯地發現

              public static final int NORMAL_WATCH = 0;

               // 當主題變化時,通過電子郵件通知用戶

              public static final int EMAIL_NOTIFY_WATCH = 1;

              //設置一個主題被觀察的時間,默認為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的內容

              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;

              …

           

              //為某個主題創建一個觀察者

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

                      throws UnauthorizedException;

              //刪除某個主題的觀察者

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

              //得到一個主題的所有觀察者

              public Iterator getWatchedForumThreads(User user, int watchType)

                      throws UnauthorizedException;

              //判斷一個用戶是否在觀察監視該主題

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

                      throws UnauthorizedException;

              …

          }

          DbWatchManager是WatchManager的一個子類,通過數據庫保存著有關某個主題被哪些用戶監視等數據資料。WatchManager對象是隨同DbForumFactory()一起生成的。

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

          protected void notifyWatches(ForumThread thread) {

               //If watches are turned on.

              if (!emailNotifyEnabled) {

                      return;

               }

               //通知所有觀察這個主題的用戶

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

               TaskEngine.addTask(task);

           }

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

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

          factory.watchManager.notifyWatches(this);

          這其實是調用了DbWatchManager的notifyWatches方法,因此確實是在增加新帖子時觸發了該帖子的所有觀察者。

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

          public class TaskEngine {

              //任務列表

              private static LinkedList taskList = null;

              //工作數組

              private static Thread[] workers = null;

              private static Timer taskTimer = null;

              private static Object lock = new Object();

           

              static {

                  //根據配置文件初始化任務啟動時間

                  taskTimer = new Timer(true);

                  // 默認使用7個線程來裝載啟動任務

                  workers = new Thread[7];

                  taskList = new LinkedList();

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

                       // TaskEngineWorker是個簡單的線程類

                      TaskEngineWorker worker = new TaskEngineWorker();

                      workers[i] = new Thread(worker);

                      workers[i].setDaemon(true);

                      workers[i].start();         //啟動TaskEngineWorker這個線程

                  }

              }

              //TaskEngineWorker內部類

              private static class TaskEngineWorker implements Runnable {

                  private boolean done = false;

                  public void run() {

                      while (!done) {

                          //運行nextTask方法

                          nextTask().run();

                      }

                  }

              }

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

              private static Runnable nextTask() {

                  synchronized(lock) {

                      // 如果沒有任務,就鎖定在這里

                      while (taskList.isEmpty()) {

                          try {

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

                          } catch (InterruptedException ie) { }

                      }

                      //從任務列表中取出第一個任務線程

                      return (Runnable)taskList.removeLast();

                  }

              }

              public static void addTask(Runnable r) {

                  addTask(r, Thread.NORM_PRIORITY);

              }

              //這是任務列表Queue的生產者

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

                  synchronized(lock) {

                      taskList.addFirst(task);

                      //提醒所有鎖在lock這里的線程可以運行了

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

                      lock.notifyAll();

                  }

              }

              …

          }

          在TaskEngine中啟動設置了一個消息管道Queue和兩個線程。一個線程是負責向Queue里放入 Object,可謂是消息的生產者;而另外一個線程負責從Queue中取出Object,如果Queue中沒有Object,那它就鎖定(Block)在 那里,直到Queue中有Object,因為這些Object本身也是線程,因此它取出后就直接運行它們。

          這個TaskEngine建立的模型非常類似JMS(Java消息系統),雖然它們功能類似,但不同的是: JMS是一個分布式消息發布機制,可以在多臺服務器上運行,處理能力要強大得多。而TaskEngine由于基于線程基礎,因此不能跨JVM實現。可以說 TaskEngine是一個微觀組件,而JMS則是一個宏觀架構系統。JMS相關討論將在后面章節進行。

          以上討論了Jive系統中觀察者模式的實現,Jive使用線程比較基礎的概念實現了觀察者模式,當然有助于了解J2EE很多底層的基礎知識,整個Web容器的技術實現就是基于線程池原理建立的。

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

          setChanged();

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

          而觀察者只要實現Observer接口,并實現update方法,在update方法中將被觀察者觸發后傳來的object進行處理。舉例如下:

          網上商店中商品價格可能發生變化,如果需要在價格變化時,首頁能夠自動顯示這些降價產品,那么使用觀察者模式將方便得多。首先,商品是一個被觀察者:

          public class product extends Observable{

            private float price;

            public float getPrice(){ return price;}

            public void setPrice(){

             this.price=price;

           //商品價格發生變化,觸發觀察者

             setChanged();

             notifyObservers(new Float(price));

            }

            ...

          }

          價格觀察者實現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);

              }

            }

          }

          這樣,一個簡單的觀察者模式就很容易地實現了。

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

          前言

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

          再開始之前,需要指出的是,Jive中對Design Pattern的應用,并沒有拘禮與GOF書中所給出的實現方法,而是有許多變通的地方。一方面,我想是由于具體的實際需要,另一方面,我想這也是設計觀念進化的結果吧。因而,這些變通的地方,將是我講解的重點。





          回頁首


          整體結構概敘

          基于一個OO的設計原則:面向接口編程,而不是針對實現編程。Jive在設計的時候,把其大部分的基本對象都設計為接口或者抽象類。在Jive中,基本的接口有Forum,ForumMessage,ForumThread,Group,User,Authorization和Query。我們可以很容易的從這些接口的名字來知道他們的功用,下面的類圖給出了這些類之間的一些靜態關系:



          圖1:Jive整體關系
          圖1:Jive整體關系

          你可能會有疑問,為什么會都是接口呢?這是基于擴展性考慮的。在Jive給出的實現中,所有的這些接口,Forum,ForumMessage,User等等,都使用數據庫來實現的,一條消息,或者一個用戶對應于數據庫中的一條消息Jive使用了DbForum,DbForumMessage,DbUser等類來實現這些接口,通過JDBC來操作數據庫,使之作為論壇的底層支撐。

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

          下面來看看具體的設計和編碼。






          AbstractFactory模式和可擴展性

          如果要實現較好的可擴展性,AbstractFactory模式確實是一件利器。如上面所說,如果要創建的Forum接口的不同實現,而又不想更改代碼的話,就需要用到抽象工廠了。再Jive中,AuthorizationFactory類是一個抽象類,用來創建Authorization對象。這是一個抽象工廠,可以通過不同的子類來創建不同的Authorization對象。這個工廠的實現方法是:

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

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

          然后是用一個private static的loadAuthorizationFactory方法來給這個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方法返回一個Authorization的過程中,先初始化工廠類factory變量,然后用factory的createAuthorization方法來創建:

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

          不同的子類有不同的createAuthorization方法的實現。比如在DbAuthorizationFactory這個AuthorizationFactory的數據庫實現子類中,createAuthorization方法是這樣實現的:

             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);
                      }

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

          還有就是靜態方法的使用,使得這個類看起來有些Singleton的意味。這使得對于AbstractFactory的創建變得簡單。

          下面的類圖給出了這個AbstractFactory的實現的總體情況:



          圖2:AbstractFactory模式的實現類圖
          圖2:AbstractFactory模式的實現類圖

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

          這樣,在需要生成一個Authorization的時候,只需要調用AuthorizationFactory的靜態方法getAuthorization就可以了,由子類實現了具體的細節。

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





          回頁首


          Proxy模式和權限控制

          Proxy模式的功能有很多,比如遠程代理,用來給遠程對象提供一個本地代表;虛代理,用來為創建開大開銷的對象提供緩沖,等等。在Jive中使用的是保護代理,為被保護的對象提供權限控制。

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

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

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

          在它的構造方法中,就提供了一個ForumFactory對象,這是需要被代理的對象;一個Authorization對象,提供用戶信息;還有一個ForumPermissions,提供認證信息:

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

          一般的代理過程都是這樣的,在訪問某個方法之前,必須接受權限的檢查,以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();
                      }
                      }

          下面給出這個模式的類圖:



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

          這個模式的實現基本上和GOF中所給出的實現一致。在Jive中,幾乎所有的接口,Forum,ForumMessage,ForumThread等等,都會有一個相應的Proxy對象來進行權限控制。而在創建具體的對象的時候,都是用相應的Proxy對象來代替原有的對象返回的。例如在ForumFactory的getInstance()方法中需要返回一個Forum的時候,Jive是這樣做的:

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

          因而,所有被創建的對象實際上都是Proxy對象,抽象工廠保證了沒有權限驗證的對象根本不會客戶所得到,它們只會在Proxy的內部扮演角色,而永遠不會被外部對象所存取,這樣,就從根本上保證了論壇的安全。





          回頁首


          Decorator模式和過濾器

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

          Decorator模式相當于封裝了某個特定的操作,當某個對象需要這個操作的時候,加上這個Decorator即可。并且,多個Decorator還可以組合,以提供更多的功能。

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

          Jive中,所有的過濾器繼承于一個抽象類ForumMessageFilter,而ForumMessageFilter又實現了ForumMessage接口。也就是說,每一個過濾器實際上也是一個ForumMessage對象。

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

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

          highlightCode是一個private方法,實施具體的過濾的細節。getBody()方法實際上是定義在ForumMessage接口中的,當調用過濾器的getBody()方法時,就能夠得到結構重整后的ForumMessage對象了。這個對象可以被其他客戶引用,也可以在傳遞給另外的過濾器,實施進一步的操作。

          在實現一個具體的消息的過濾的時候,在Forum中有addForumMessageFilter(),applyFilters()方法,用來實現對過濾器的應用。

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

             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()方法,為過濾器復制消息體。這個方法的使用,分離了在過濾器中對于消息體和過濾規則的初始化過程,這也是一個值得借鑒的技巧!

          下面給出Decorator模式的類圖:



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

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





          回頁首


          Iterator模式和論壇的瀏覽

          Iterator模式用來分離數據結構和遍歷算法,降低兩者之間的耦合度,以使得同一個數據結構用不同的算法遍歷時,仍能夠具有相同的接口,另一方面,Iterator模式使得當改換遍歷算法后,不需要更改程序的代碼。

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

          對于論壇中的基本對象Forum,ForumThread,ForumMessage,Group,User都有相應的遍歷器。比如對應于Forum接口有ForumIteratorProxy對象。這個ForumIteratorProxy遍歷器就相當于一個封裝了一系列Forum對象的集合類,通過定義好的接口hasNext()和next()可以方便的遍歷這個集合,而并不需要知道是如何遍歷這個集合的。遍歷的算法可能很簡單,也可能很復雜,但是對于外部的客戶而言,這并沒有任何的區別。

          而對于論壇中具體的遍歷方法,這取決于具體的實現,在Jive中給出的是數據庫的實現。

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

          DbThreadIterator對象實現了Iterator接口,是對于一個Thread中所有Message的遍歷器,我們來看看它是如何實現的。

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

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

          next()方法從數據庫中取出與在這個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;
                      }

          這樣,通過對數據庫的操作,DbThreadIterator實現了對一個Thread中所有Message遍歷的方法。

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

          在DbForumThread中實現了這個方法:
          public Iterator messages() {return new DbThreadIterator(this);}

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

          下面的類圖給出了在Jive中Iterator模式的實現方法:



          圖5:Jive中Iterator模式的實現
          圖5:Jive中Iterator模式的實現

          在Jive中,因為在一個Thread之下,Message是按樹形結構組織的,因而,當需要層級表示一個Thread中的Message之間的關系的時候,僅僅用上面講到的線性的Iterator是不夠的。這時候,對Iterator的概念進行推廣,就引入了TreeWalker接口。

          顧名思義,TreeWalker提供了遍歷一個樹和存取樹上節點的方法:

          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應用的那么廣泛,而且,也可以很容易的在TreeWalker上面在套一層Iterator的借口,讓它在某些情況下行使Iterator的職責。這兒就不再多討論了。

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

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

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

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

          二.JDK的安裝配置.
          一般以root用戶安裝。
          先從SUN網站上下載一個jdk.比如:j2sdk-1_3_1_06-linux-i586.bin,放到/usr/local
          下,
            chmod a+x j2sdk-1_3_0-linux.bin(添加執行權限)
            ./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

          設置環境變量:
            # 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網站下載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用戶登陸,創建oracle用戶,目錄,設置oracle環境變量.
            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

          調整內存
            Shared Memory
            su root
            # vi/etc/sysctl.conf里添加
            kernel.shmmax=1073741824
          4.安裝oracle
            進入Disk1目錄
            cd Disk1
            在控制臺窗口敲入
            ./runInstaller
          安裝完了以后,啟動數據庫
          oracle$ sqlplus /nolog
          SQL> connect / as sysdba
          SQL> startup

          oracle 的安裝過程比較復雜,而且如果你的開發包安裝的不夠全的話,會出現一些錯誤,具體請參考: http://www.puschitz.com/InstallingOracle9i.shtml

          5.設置oracle 自啟動

          (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 下創建一個dbora文件。
          內容如下:

            #!/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

          把這個文件與下列文件聯接:
          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
          重啟動服務器,檢查數據庫是否已經起來。

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

          五.設置WebLogic Server 自啟動.
          以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時候的用戶名.)
          放到/etc/rc.d/rc.local里就行了,不過這樣有一個缺點,你WebLogic Server啟動后一直在后臺運行,你不能看到上面的提示信息和出錯信息.

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


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

          1.安裝JDK
          首先,到http://java.sun.com/j2se/1.5.0/download.jsp
          下載最新版本的Linux 平臺的JDK,建議下載RPM自解壓格式的例如本文所用jdk-1_5_0_06-linux-i586-rpm.bin,先下載文件到/tmp,打開終端,輸入:
          cd /tmp
          su
          輸入root密碼
          直接執行文件:
          ./jdk-1_5_0_06-linux-i586-rpm.bin
          然后會出現sun的協議(Sun Microsystems, Inc. Binary Code License Agreement),如果運行jdk-1_5_0_06-linux-i586-rpm.bin無效請給予其相應的運行權限。
          想查看完整協議,不斷點擊more就可以了。如果看完了,或者像我一樣看不懂^__^就直接按q吧。
          出現提示:Do you agree to the above license terms? [yes or no]
          如果同意協議,請輸入yes
          然后自動解壓出jdk-1_5_0_06-linux-i586.rpm,并且自動安裝,如果不是root用戶,可能會出現類似
          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
          的錯誤。
          最后顯示Done.安裝結束

          2.配置環境變量
          就像在windows下一樣,裝好JDK需要配置環境變量,否則系統找不到相應的程序。先查看當前系統環境變量中jdk的路徑:
          echo $JAVA_HOME
          如果安裝SuSE Linux時選擇了相應的java的包,則顯示/usr/lib/jvm/java。再看當前JDK版本:
          java -version
          我的機器上顯示如下信息:
          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版本,不是我們剛才安裝的版本(因為沒有修改環境變量嘛)。
          我們剛才安裝的版本默認在/usr/java/jdk1.5.0_06,我們把它加到環境變量。
          最簡單的辦法就是編輯/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
          作用分別是設置 JAVA_HOME , CLASSPATH , JRE_HOME , PATH 所指向的路徑。跟windows下的意義一樣。如果不懂可以查閱相關文檔或者直接把以上文本復制粘貼到你的/ect/profile 中即可。
          注銷一下,使更改生效。
          再查看一下當前的環境變量:
          echo $JAVA_HOME
          輸出:
          /usr/java/jdk1.5.0_06
          可以看到我們剛才裝的JDK生效了。
          然后輸入:
          java -version
          查看當前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)
          說明我們環境變量配置成功了。

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

          1 環境的搭建

          要使用JAVA開發Web應用,必需要JAVA的運行環境,還有開發環境。當然Web開發少不了數據庫。Web程序要運行也少不了Web服務器。

          這里我們選用JAVA運行環境:J2SDk1.4

          數據庫:Mysql 4.0.15

          Web服務器:Tomcat 4.1.18

          1.1 JAVA的運行環境

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

          執行二進制文件即可解壓縮文件:

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

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

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

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

          然后加入環境變量

          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

          現在JAVA運行環境就搭建好了,你可以試試寫一個java程序來驗證

          [root@localhost jdk]#vi HelloWorld.java

          輸入如下內容

          public class HelloWorld{

             public static void main(String args[]){

              System.out.println("HelloWrold");

             }

          }

          :wq

          寫盤并退出編輯器

          [root@localhost jdk]#javac HelloWorld.java

          沒有錯誤

          [root@localhost jdk]#java HelloWorld

          Hello,World

          恭喜,

          Kq[業Kk0+?bv

          你的JAVA運行環境搭建好了。現在進入下一步。

           

          1.2 Mysql數據庫安裝

          1下載數據庫安裝文件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環境變量

          [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

          進入解壓縮得到的目錄

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

          5配置發行版本并且編譯

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

          [root@localhost mysql-4.0.15a]#make

          當你運行configure時,你可能想要指定一些選項,--prefix選項制定安裝mysql的目錄為/usr/local/mysql

          6安裝所有東西

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

          你可能需要root用戶來運行這個命令

          ok現在mysql數據庫服務器就安裝好了。接下來還有重要的步驟需要執行

          7創造MySQL授權表(只有你以前沒安裝MySQL是必需的):

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

          8做完上面的步驟我們就可以啟動mysql進行操作了。

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

          [root@localhost bin]# ./mysqld_safe &

          如果沒有出錯提示,查看一下mysql進程

          [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的進程了。

          現在就可以使用mysql,root登陸MySQL服務器

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

          這里會提示輸入密碼默認的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服務器了。

          mysql> show databases;

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

          | Database |

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

          | cumt     |

          | mysql    |

          | test     |

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

          3 rows in set (0.01 sec)

          現在就可以建立數據庫了。這里就不介紹怎樣建立數據庫和建立表了。

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

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

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

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

          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的權限問題具體的講解請參考MySQL參考手冊的存取權限系統

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

          ,G專}Uc{|g[

          你也可以建立一個新用戶來連接

           

          登陸到mysql服務器

          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使得它可以從任何地方連接服務器的一個完全的超級用戶,但是必須使用一個口令cumt做這個。現在我們就可以從程序中用cumt來連接數據庫了。

          但是在程序中還是沒有對表的寫權限。這是由于我們的數據庫用戶是root而不是我們建立的mysql組的mysql用戶。所以只有讀的權限而沒有寫的權限。我們需要把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連接數據庫和操作數據庫了。

          1.3 Web服務器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


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

          2.
          如要求系統開機自動tomcat /etc/rc.d/rc.local中加入:
          /usr/local/tomcat/bin/startup.sh

          3.
          對于linux7.1系統, tomcat好象不能正常啟動,需要安裝:
          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)
          重新啟動Tomcat. class文件放在/home/bqlr/myweb/WEB-INF/classes目錄下 jsp文件放在/home/bqlr/myweb

          posted @ 2007-05-14 10:49 edsonjava 閱讀(394) | 評論 (0)編輯 收藏
           
          主站蜘蛛池模板: 阳高县| 嘉峪关市| 石台县| 崇文区| 馆陶县| 叙永县| 波密县| 横峰县| 乌审旗| 广汉市| 洛扎县| 历史| 堆龙德庆县| 彰武县| 治县。| 普陀区| 景宁| 东兴市| 黎川县| 临西县| 屏山县| 宁德市| 沙湾县| 蓝山县| 井陉县| 绵阳市| 六安市| 岳普湖县| 汝城县| 兴和县| 金寨县| 吴旗县| 新巴尔虎左旗| 金平| 吴江市| 上栗县| 新宁县| 新蔡县| 永兴县| 莱芜市| 凤阳县|