Monday, February 20, 2006

若有某些網頁須要關閉 cache 功能或希望 proxy server 不要保留資料.
這裡有些文件對這方面有詳細說明

這裡介紹一下 jsp 的寫法, 其它web 開發應該也可以比照辦理.


<%
//no cache
response.setHeader("Pragma", "no-cache");
response.setHeader("Cache-Control", "no-cache");

//causes the proxy cache to see the page as "stale"
response.setDateHeader("Expires", 0);
%>

Ajax 利用 prototype javascript framework

prototype 是一個十分優秀的 javascript framework, 他會改變你寫 javascript 的想法以及重新思考如何寫出較為 OO 的 javascript 以方便程式開發及維護.

他有一點點不是很容易上手這裡有一些文件可供參考

我們在 RUNEC/WE 程式裡已經架在 prototype 上開發.

  • 很 OO 你可以寫一個 javascript class 專門處理相關工作
  • 可以用 bindAsEventListener 將某個 button event 處理程式連回你的 javascript class
  • $(), $F()... 程式較乾淨

重點不在這, 介紹一下簡單的 AJAX 應用, 你可以先到 wikipedia 看一下相關文章對 AJAX 的介紹後有些概念其它工作就簡單一點. 其實會需要寫一些 javascipt 來處理, 所以一個好的 javascript framework 是需要的.

  • Client 端利用 prototype Ajax.Request 將參數送到某個 url 傳送到 Server 端
    當按下某個 button 後 javascript 會送出 HTTPXMLRequest 給 Server
  • Server 處理後將結果轉成 xml 送回 client
  • 然後 client 端 onSuccess 時收到 xml 資料, 再做處理
    //button for xxx
    //"src" is the xxx button element
    //"next" is a boolean variable to decide which type we used function
    btnXXX(src, next) {
    //找到 form 以便取得 form 內其它參數值
    var form = src.form;
    var opt = {
    method: 'post',

    // 處理接收到的 xml 資料
    onSuccess: function(x) {
    //alert("Received data"+x.responseText);
    var dt = x.responseXML.getElementsByTagName("docType")[0];
    dt = dt.childNodes[0].nodeValue;
    var id = x.responseXML.getElementsByTagName("id")[0];
    id = id.childNodes[0].nodeValue;
    dt = dt.replace(/^\s+/g, '').replace(/\s+$/g, ''); //trim
    id = id.replace(/^\s+/g, '').replace(/\s+$/g, ''); //trim

    //利用 javascript 處理所取得的資料
    var uri = '/id/view/'+dt+'/'+id;
    document.location.replace(uri);
    },

    // Handle 404
    on404: function(x) {
    alert('Error 404: location "' + x.statusText + '" was not found.');
    },

    // Handle other errors
    onFailure: function(x) {
    alert('Error ' + x.status + ' -- ' + x.statusText);
    }
    };


    //處理參數, 從 form 中取得 id & i_doc_type 兩個 input 值
    //$F(xxx) 可以取出 input 值
    opt.postBody = 'type='+((next) ? encodeURIComponent('next') : encodeURIComponent('prev'))+
    '&docType='+encodeURIComponent($F(form.elements['i_doc_type']))+
    '&id='+encodeURIComponent($F(form.elements['id']));

    //送出到參數到 url
    new Ajax.Request('/biz/trans/transNextPrev.xml.jsp', opt);
    }
  • Server 端對此 url 及參數處理後, 要輸出 xml 結果
    這和一般開發 Web 程式一樣, 只要能輸出 xml 結果即可
    你可以試試 openrico 的一個輸出 xml 結果資料, Try it.
    這是我們 JSP/Servelet 的處理

<?xml version="1.0" encoding="UTF-8"?>
<%@ page contentType="text/xml;charset=UTF-8" pageEncoding="UTF-8" %>
<%@ taglib uri="/WEB-INF/tld/sat/core.tld" prefix="c" %>
<%@ taglib uri="/WEB-INF/tld/sat/xml.tld" prefix="x" %>
<c:header name="Cache-Control" value="no-cache"/>
<c:header name="Pragma" value="no-cache"/>

<c:doc docType="biz.trans.TransNextPrev">
<c:set var="rows"
value="=dataSource('com.potix.sat.report.biz.trans.TransNextPrevDataSource')"/>

<transNextPrev>
<c:for-each-row value="=rows" docType="biz.trans.TransNextPrevRow">
<docType><c:out value="=docType"/></docType>
<id><c:out value="=t"/></id>
</c:for-each-row>
</transNextPrev>
</c:doc>
  • 以下是產生出來的 xml 檔

  • <?xml version="1.0" encoding="UTF-8" ?>
    <transNextPrev>
    <docType>acc.trans.Entry</docType>
    <id>060113001</id>
    </transNextPrev>

  • Client 端要處理收到的 xml 資料
    見 btnXXX() 內 onSuccess 處理

Lucene Java 下的全文搜尋的使用

Lucene 是一個很棒的全文搜尋程式庫, 看過一些文件說明及測試後,
我們說明一下如何整合進入我們的產品 RUNEC/WE.
  1. 把物件加入索引
    • 我們所有想要做索引的物件須要有一個唯一 oid/uuid/guid, 因為我們須要有辦法將物件和索引串在一起.
    • getIndexDir() 是放置 Lucene 產生的索引目錄
    • getAnalyzer() 我們是採用 StandardAnalyzer, 你可以考慮 CJKAnalyzer
    • o 是要索引的物件
    • 範例
      protected void addIndex(Object o) {
      boolean create = false;
      if (!IndexReader.indexExists(getIndexDir()))
      create = true;


      IndexWriter w = null;
      try {
      w = new IndexWriter(getIndexDir(), getAnalyzer(), create);
      w.mergeFactor = 20; //default:10

      final Document ld = new Document();
      //oid
      ld.add( Field.Keyword("oid", o.oid().toString()) );
      //others to do index
      ld.add( Field.Keyword("title", o.getTitle()) );
      ld.add( Field.Text("content", o.getMessage()) );
      ld.add( Field.Text("content", o.getAppendix()) );

      //...
      w.addDocument(ld);
      w.optimize();
      } catch (IOException ex) {
      throw SystemException.Aide.wrap(ex);
      } finally {
      try {
      if (w != null)
      w.close();
      } catch (IOException ex) {
      throw SystemException.Aide.wrap(ex);
      }
      }
      }
  2. 若物件被刪除時要記得刪除索引
    • 利用 ooid 找到索引後刪除
    • 範例
      protected void removeIndex(Object o) {
      if (!IndexReader.indexExists(getIndexDir()))
      return; IndexReader r = null;

      try {
      r = IndexReader.open(getIndexDir());

      final Term term = new Term("oid", o.oid().toString());
      r.delete(term);
      } catch (IOException ex) {
      throw SystemException.Aide.wrap(ex);
      } finally {
      try {
      if (r != null)
      r.close();
      } catch (IOException ex) {
      throw SystemException.Aide.wrap(ex);
      }
      }
      }
  3. 若物件被修改也要記得修正索引內容
    • Lucene 找不到修正索引的方法, 所以只能利用 o 的 oid 找到索引後刪除再重新加入, 1.+2. 即可
  4. 索引的搜尋
    • Lucene 支援蠻複雜的搜尋語, 我們只用簡單的就夠了
    • "keyword" 是要搜尋的字串
    • 範例
      Hits hits = null;
      try {
      final Query qt = QueryParser.parse(keyword, "title", getAnalyzer());
      final Query qc = QueryParser.parse(keyword, "content", getAnalyzer());

      final BooleanQuery q = new BooleanQuery();
      q.add(qt, false, false);
      q.add(qc, false, false);

      final IndexSearcher s = new IndexSearcher(getIndexDir());
      hits = s.search(q);
      } catch (ParseException ex) {
      throw SystemException.Aide.wrap(ex);
      } catch (IOException ex) {
      throw SystemException.Aide.wrap(ex);
      }
    • 範例, n 為搜尋結果第幾筆資料
      try {
      final Document ld = hits.doc(n);
      final float score = hits.score(n);

      final String oid = ld.get("oid");
      //you can look for o from oid here

      final String title = ld.get("title");
      final String[] cts = ld.getValues("content");
      final StringBuffer sb = new StringBuffer();
      for (int i = 0; i < cts.length; i++)
      sb.append(cts[i]).append(' ');
      } catch (IOException ex) {
      throw SystemException.Aide.wrap(ex);
      }
  5. Luke 是一個不錯的工具程式, 可以查看 Lucene 產生的索引目錄下的狀況, 可以幫你除錯.

JavaMail 替代品 Jakarta Commons Email 的使用及中文問題

Jakarta Commons Email 是一個蠻方便使用的程式庫, 可使讓你輕鬆發送電子郵件.

安裝
我們的應用程式 RUNEC/WE 是 Jboss 環境, 把 commons-email-1.0.jar 放在 JBOSS/server/xxx/lib 下, 它還需要一些 JavaMail 程式庫 ex. activation.jar 也記得放進去.

使用
  1. SimpleEmail 使用例, 請看 http://jakarta.apache.org/commons/email/examples.html
  2. 為了解中文問題, 下面是我們的方法
    • 用 MultiPartEmail
    • 用 UTF-8
    • 範例
      final MultiPartEmail email = new MultiPartEmail();
      email.setHostName(smtp); //smtp server: mail.potix.com
      email.setSmtpPort(port); //port: 25
      email.setCharset("UTF-8");


      email.setFrom(from, fromName);
      email.addTo(to, toName);
      email.addCc(cc, ccName);

      email.setSubject(subject);

      //message, 中文 UTF-8 內容
      email.addPart(message, "text/plain;charset=UTF-8");
      email.send();
  3. 最後我們的應用程式採用 HtmlEmail 加上 Velocity 做模板, 輸出 html 格式電子郵件.
  4. 若你的 html 郵件有影像...要送的話, 你可以參考 1. 文件說明試試,

PDFCreator - 製作 pdf 檔的工具程式

PDFCreator 是一個免費的工具可以建立 pdf 格式的檔案. 
PDFCreator is a free tool to create PDFs easily from nearly any application.
With the PDFCreator Printer driver you turn any program into a PDF-machine.

安裝法
1. 下載 PDFCreator-0_8_0_AFPLGhostscript.exe 後安裝
2. 下載 Patch02-PDFCreator-0_8_0.exe 後安裝此修正檔
3. reboot

使用法
任何應用程式使用 "列印" 時選 "PDFCreator" 印表機,
會出現輸入 pdf 檔名對話框後可以建立 pdf 檔案.
但是卻無法產生正確的 pdf 檔, 會有 stack error 訊息.

ERROR: undefined
OFFENDING COMMAND: ??
STACK:

設定
這時你要調整一下 "PDFCreator" 印表機設定才能正確運作
進入 [控制台]/[印表機] 找到 "PDFCreator" 印表機,
右鍵進入 [內容]/[進階]

驅動程式原為 "PDFCreator" 要新增驅動程式,
選擇「製造商:Apple」「印表機:Apple Color LW 12/660 PS」

最後再將 "Apple Color LW 12/660 PS" 更名回 "PDFCreator" 印表機即可!